UNPKG

gold-sight

Version:

Test your code on realistic content, precisely

336 lines 14 kB
import { prettyFormat } from "./utils/prettyFormat"; let fs; let path; if (process.env.NODE_ENV === "test") { fs = await import("fs"); path = await import("path"); } import { deepClone } from "./utils/deepClone"; import { getEventBus, EventBus, getEventByState, getEventByPayload, getEvent, getEventUUID, getEventByUUID, filterEventsByUUID, withEventBus, withEvents, withEventNames, withEventNamesList, filterEventsByPayload, filterEventsByState, filterEvents, makeEventContext, getFuncData, } from "./utils/eventBus"; import { AbsCounter } from "./utils/absCounter"; const assertionQueues = {}; let globalConfig = {}; if (fs && path) { const globalConfigFilePath = path.resolve(process.cwd(), "gold-sight.config.json"); if (fs.existsSync(globalConfigFilePath)) { globalConfig = JSON.parse(fs.readFileSync(globalConfigFilePath, { encoding: "utf-8" })); } } class AssertionMaster { get globalOptions() { return { ...(this._globalOptions || {}) }; } constructor(assertionChains, globalKey, globalOptions) { this.resetState = () => { this._state = { ...this.newState(), master: this.master, callStack: [], branchCounter: new Map(), queueIndex: 0, uuidStack: [], funcCounter: new Map(), }; }; this.assertQueue = (options) => { options = { logMasterName: this._globalKey, errorAlgorithm: "firstOfDeepest", verbose: true, ...(globalConfig?.assert || {}), ...(this._globalOptions?.assert || {}), ...(options || {}), }; const assertionQueue = assertionQueues[this.globalKey]; const allAssertions = Array.from(assertionQueue.values()); const verifiedAssertions = new Map(); if (!this.state?.master && options?.master === undefined) console.error(`No master indexes set. Provide it via options.`); const master = options?.master ?? this.state?.master; console.groupCollapsed(`✅ ${options.logMasterName} - ✨${printMaster(options.master)}`); const errors = []; // Step 1: Group items by function name let groupedByName = {}; for (const [, item] of assertionQueue.entries()) { const { state, args, result, address, name } = item; const assertions = this.assertionChains[name]; if (!assertions) { throw Error(`Assertion chain for ${name} not found. Are you setting up the default assertion chains?`); } for (const [key, assertion] of Object.entries(assertions)) { let didRun = false; try { didRun = assertion(state, args, result, allAssertions); } catch (e) { const err = e; let prelog = ""; if (master) { prelog = `Master:${master.index}`; if (master.step) { prelog += `, Step:${master.step}`; } prelog += `, ${name} #${item.funcID}`; } if (address) { const formattedAddress = typeof address === "object" ? prettyFormat(address, { printBasicPrototype: false, }) : address; prelog += `, ${formattedAddress}`; } if (prelog) { prelog += ", "; err.message = `${prelog}${err.message}`; } errors.push({ err, ...item }); } didRun = didRun; // if (didRun) { let count = verifiedAssertions.get(key) || 0; count++; verifiedAssertions.set(key, count); //} } if (!groupedByName[item.name]) groupedByName[item.name] = []; groupedByName[item.name].push(...errors.filter((err) => err.name === item.name)); } if (options.targetName) { if (groupedByName.hasOwnProperty(options.targetName)) groupedByName = { [options.targetName]: groupedByName[options.targetName], }; } // Step 2: Determine the highest funcIndex for each name const nameWithHighestIndex = Object.entries(groupedByName).map(([name, items]) => ({ name, highestIndex: Math.max(...items.map((i) => i.funcIndex)), })); // Step 3: Sort names based on their highest funcIndex nameWithHighestIndex.sort((a, b) => { return b.highestIndex - a.highestIndex; }); if (options.verbose) { for (const [key, count] of verifiedAssertions.entries()) { console.log(`✅ ${key} - ✨${count} times`); } } outer: for (const { name, highestIndex } of nameWithHighestIndex) { let items = groupedByName[name]; if (options.errorAlgorithm === "deepest") items = items.filter((item) => item.funcIndex === highestIndex); items.sort((a, b) => { return a.funcID - b.funcID; }); if (!options.showAllErrors) { for (const err of items) { throw err.err; } } } console.groupEnd(); if (errors.length) { if (options.showAllErrors) { throw new Error(errors .map((e) => `${e.name}:${e.funcIndex}:${e.branchCount}${e.err.message}`) .join("\n")); } } return verifiedAssertions; }; this.assertionChains = assertionChains; this._globalKey = globalKey; this._globalOptions = globalOptions; assertionQueues[globalKey] = new Map(); } get globalKey() { return this._globalKey; } set master(master) { this._master = master; } get master() { return this._master; } get state() { return this._state; } wrapFn(fn, name, processors) { return ((...args) => { const parentId = this.state.callStack[this.state.callStack.length - 1] ?? -1; let funcIndex = parentId + 1; const eventBus = getEventBus(args); let eventUUID; if (eventBus) { eventUUID = crypto.randomUUID().toString(); this.state.uuidStack.push(eventUUID); for (let i = 0; i < args.length; i++) { const arg = args[i]; if (typeof arg === "object" && "eventUUID" in arg) { args[i] = { ...arg, eventUUID, eventUUIDs: [...this.state.uuidStack], funcData: { funcName: name, funcIndex: funcIndex }, }; break; } } } const convertedArgs = processors?.argsConverter ? processors.argsConverter(args) : args; if (processors?.pre) processors.pre(this.state, convertedArgs); const deepCloneOpts = { result: false, args: false, ...(globalConfig?.deepClone || {}), ...(this._globalOptions?.deepClone || {}), ...(processors?.deepClone || {}), }; const argsClone = deepCloneOpts.args ? deepClone(convertedArgs) : convertedArgs; if (!this.state) throw new Error("State is not initialized. The top function wrapper may not be executing"); const queueIndex = this.state.queueIndex; this.state.queueIndex++; let funcCounter = this.state.funcCounter.get(name) || 1; this.state.funcCounter.set(name, funcCounter + 1); const funcID = funcCounter; this.state.callStack.push(funcIndex); const branchCount = this.state.branchCounter.get(parentId) || 0; this.state.branchCounter.set(parentId, branchCount + 1); const result = fn(...args); this.state.callStack.pop(); if (eventUUID) this.state.uuidStack.pop(); function processResult(result) { const convertedResult = processors?.resultConverter ? processors.resultConverter(result, args) : result; const resultClone = deepCloneOpts.result ? deepClone(convertedResult) : convertedResult; return resultClone; } const isAsync = fn.constructor.name === "AsyncFunction"; const finalResult = isAsync ? result : processResult(result); const assertionData = { state: this.state, funcIndex, result: finalResult, name, branchCount, args: argsClone, eventBus, eventUUID, funcID, postOp: () => { }, }; let originalResult = result; if (fn.constructor.name === "AsyncFunction") { result.then((r) => { originalResult = r; assertionData.result = processResult(r); }); } assertionData.postOp = (state) => { this.runPostOp(state, args, originalResult, processors, assertionData); }; assertionQueues[this.globalKey].set(queueIndex, assertionData); return isAsync ? result : result; }); } wrapAll() { } reset() { const assertionQueue = assertionQueues[this.globalKey]; assertionQueue.clear(); } setQueue(queue) { assertionQueues[this.globalKey] = queue; } getQueue() { return getQueue(this.globalKey); } setQueueFromArray(queue) { assertionQueues[this.globalKey] = new Map(queue); } runPostOps() { let assertionQueue = assertionQueues[this.globalKey]; const sortedEntries = Array.from(assertionQueue.entries()).sort(([a], [b]) => a - b); assertionQueue = new Map(sortedEntries); assertionQueues[this.globalKey] = assertionQueue; for (const value of assertionQueue.values()) { value.state = { ...value.state }; if (value.eventBus && value.eventUUID) { const events = value.eventBus.getAllEventsForUUID(value.eventUUID); for (const event of events) { event.state = value.state; } } if (value.postOp) value.postOp(this.state, value.args, value.result); } } runPostOp(state, args, result, processors, assertionData) { assertionData.address = processors?.getAddress ? processors.getAddress(state, args, result) : ""; assertionData.snapshot = processors?.getSnapshot ? processors.getSnapshot(state, args, result) : this._globalOptions?.getSnapshot ? this._globalOptions.getSnapshot(state, args, result) : globalConfig?.getSnapshot ? globalConfig.getSnapshot(state, args, result) : undefined; if (processors?.post) processors.post(state, args, result); } wrapTopFn(fn, name, options) { return (...args) => { this.reset(); this.resetState(); const wrappedFn = this.wrapFn(fn, name, options); const result = wrappedFn(...args); this.state.master = this.master; if (result instanceof Promise) { return result.then((resolved) => { this.runPostOps(); return resolved; }); } else { this.runPostOps(); return result; } }; } } function getQueue(globalKey) { if (!assertionQueues[globalKey]) throw Error(`Assertion queue for ${globalKey} not found`); return assertionQueues[globalKey]; } function printMaster(master) { if (!master) return ""; if (master.index !== undefined && master.step !== undefined) return `Master ${master.index}, step ${master.step}`; else if (master.index !== undefined) return `Master ${master.index}`; else return ""; } function getGlobalConfig() { return globalConfig; } function setGlobalConfig(config) { globalConfig = config; } export { getQueue, deepClone, EventBus, getEventByState, getEventByPayload, getEvent, AbsCounter, getEventUUID, getEventByUUID, filterEventsByUUID, filterEventsByPayload, filterEventsByState, filterEvents, withEventBus, withEvents, withEventNames, withEventNamesList, makeEventContext, getGlobalConfig, setGlobalConfig, getFuncData, }; export default AssertionMaster; //# sourceMappingURL=index.js.map