gold-sight
Version:
Test your code on realistic content, precisely
336 lines • 14 kB
JavaScript
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