Spaces:
Build error
Build error
const path = require('path'); | |
const fs = require('fs'); | |
const crypto = require('crypto'); | |
const VM = require('../../src/virtual-machine'); | |
const JSGenerator = require('../../src/compiler/jsgen'); | |
const executeDir = path.resolve(__dirname, '../fixtures/execute'); | |
// sb2 project loading results in random IDs each time, so for now we only snapshot sb3 files | |
const testFiles = fs.readdirSync(executeDir).filter(uri => uri.endsWith('.sb3')); | |
/** | |
* @typedef {string} Snapshot Represents either a generated or parsed test case snapshot. | |
*/ | |
/** | |
* @typedef TestCase | |
* @property {string} id | |
* @property {string} file | |
* @property {object} compilerOptions | |
*/ | |
/** @type {TestCase[]} */ | |
const testCases = testFiles.map(file => ([ | |
{ | |
id: file, | |
file: file, | |
compilerOptions: { | |
warpTimer: false | |
} | |
}, | |
{ | |
id: `warp-timer/${file}`, | |
file: file, | |
compilerOptions: { | |
warpTimer: true | |
} | |
} | |
])).flat(); | |
const snapshotDir = path.resolve(__dirname, '__snapshots__'); | |
fs.mkdirSync(snapshotDir, {recursive: true}); | |
fs.mkdirSync(path.join(snapshotDir, 'warp-timer'), {recursive: true}); | |
/** | |
* @param {TestCase} testCase From testCases. | |
* @returns {Buffer} Compressed project file from disk. | |
*/ | |
const getProjectData = testCase => fs.readFileSync(path.join(executeDir, testCase.file)); | |
/** | |
* @param {TestCase} testCase From testCases. | |
* @returns {string} The path on disk where this test's snapshot should be saved. | |
*/ | |
const getSnapshotPath = testCase => path.join(snapshotDir, `${testCase.id}.tw-snapshot`); | |
const computeSHA256 = buffer => crypto | |
.createHash('SHA256') | |
.update(buffer) | |
.digest('hex'); | |
/** | |
* @param {string} snapshot a snapshot | |
* @returns {string} SHA-256 | |
*/ | |
const parseSnapshotSHA256 = snapshot => snapshot.match(/^\/\/ Input SHA-256: ([0-9a-f]{64})$/m)[1]; | |
/** | |
* @param {TestCase} testCase Test to run from testCases | |
* @returns {Promise<Snapshot>} Actual snapshot | |
*/ | |
const generateActualSnapshot = async testCase => { | |
const vm = new VM(); | |
vm.setCompilerOptions(testCase.compilerOptions); | |
const projectData = getProjectData(testCase); | |
const inputSHA256 = computeSHA256(projectData); | |
await vm.loadProject(projectData); | |
/* | |
Example source (manually formatted): | |
(function factory32(thread) { | |
const target = thread.target; | |
const runtime = target.runtime; | |
const stage = runtime.getTargetForStage(); | |
return function* gen30_whatever () { | |
// ... | |
}; | |
}; }) | |
The numbers in the function names are indeterministic, we we remove them. | |
*/ | |
const normalizeJS = source => source | |
.replace(/^\(function factory\d+/, '(function factoryXYZ') | |
.replace(/return function\* gen\d+/, 'return function* genXYZ') | |
.replace(/return function fun\d+/, 'return function funXYZ'); | |
const generatedJS = []; | |
JSGenerator.testingApparatus = { | |
report: (jsgen, factorySource) => { | |
const targetName = jsgen.target.getName(); | |
const scriptName = jsgen.script.procedureCode || 'script'; | |
const js = normalizeJS(factorySource); | |
generatedJS.push(`// ${targetName} ${scriptName}\n${js}`); | |
} | |
}; | |
vm.runtime.precompile(); | |
return `// TW Snapshot\n// Input SHA-256: ${inputSHA256}\n\n${generatedJS.join('\n\n')}\n`; | |
}; | |
/** | |
* @param {TestCase} testCase Test case from testCases | |
* @returns {Snapshot|null} Snapshot stored on disk if it exists, otherwise null. | |
*/ | |
const getExpectedSnapshot = testCase => { | |
try { | |
return fs.readFileSync(getSnapshotPath(testCase), 'utf-8'); | |
} catch (e) { | |
if (e.code === 'ENOENT') { | |
return null; | |
} | |
throw e; | |
} | |
}; | |
/** | |
* @param {Snapshot} expected from getExpectedSnapshot | |
* @param {Snapshot} actual from getActualSnapshot | |
* @returns {'VALID'|'MISSING_SNAPSHOT'|'INPUT_MODIFIED'|'INVALID'} result of comparison | |
*/ | |
const compareSnapshots = (expected, actual) => { | |
if (expected === actual) { | |
return 'VALID'; | |
} | |
if (expected === null) { | |
return 'MISSING_SNAPSHOT'; | |
} | |
const expectedSHA256 = parseSnapshotSHA256(expected); | |
const actualSHA256 = parseSnapshotSHA256(actual); | |
if (expectedSHA256 !== actualSHA256) { | |
return 'INPUT_MODIFIED'; | |
} | |
return 'INVALID'; | |
}; | |
/** | |
* Write a snapshot result to disk. | |
* @param {TestCase} testCase From testCases. | |
* @param {Snapshot} snapshot From generateActualSnapshot | |
*/ | |
const saveSnapshot = (testCase, snapshot) => { | |
fs.writeFileSync(getSnapshotPath(testCase), snapshot); | |
}; | |
module.exports = { | |
tests: testCases, | |
generateActualSnapshot, | |
getExpectedSnapshot, | |
compareSnapshots, | |
saveSnapshot | |
}; | |