Spaces:
Runtime error
Runtime 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 | |
| }; | |