From 52f39570a7cb58613b5e4f6f16bd347fd84007cd Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Wed, 2 Feb 2022 15:21:16 +0100 Subject: [PATCH] WIP, add autogenerated tests against upstream --- package.json | 7 ++++-- run.js | 37 ++--------------------------- src/astformer/index.js | 2 +- src/evaluate.js | 45 ++++++++++++++++++++++++++++++++++++ src/transformers/literals.js | 8 +++++++ tests/upstream-nix.js | 40 ++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 38 deletions(-) create mode 100644 src/evaluate.js create mode 100644 tests/upstream-nix.js diff --git a/package.json b/package.json index f742a3e..2789b11 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "tape tests/**/* | tap-difflet" }, "repository": { "type": "git", @@ -32,11 +32,14 @@ "map-obj": "^5.0.0", "match-value": "^1.1.0", "split-filter": "^1.1.3", + "tap-difflet": "^0.7.2", + "tape": "^5.5.0", "tree-sitter-javascript": "^0.19.0", "tree-sitter-nix": "cstrahan/tree-sitter-nix" }, "devDependencies": { "@joepie91/eslint-config": "^1.1.0", - "eslint": "^8.8.0" + "eslint": "^8.8.0", + "tape-catch": "^1.0.6" } } diff --git a/run.js b/run.js index 2b89f3a..89e479c 100644 --- a/run.js +++ b/run.js @@ -3,44 +3,11 @@ const fs = require("fs"); const assert = require("assert"); -const transpile = require("./src/transpile"); -const measureTime = require("./src/astformer/util/measure-time"); +const evaluate = require("./src/evaluate"); assert(process.argv[2] != null); const nixFilePath = process.argv[2]; const nixFile = fs.readFileSync(nixFilePath, "utf8"); -let transpiled = transpile(nixFile); - -const api = { - builtins: {}, - $$jsNix$memoize: function (func) { - let isCalled = false; - let storedResult; - - return function (arg) { - if (isCalled === false) { - storedResult = func(arg); - isCalled = true; - } - - return storedResult; - }; - } -}; - -// TODO: Switch to Node `vm` API instead, and check whether there's a polyfill for it for non-Node environments, build a custom one if not -const context = { module: {}, exports: {} }; -context.module.exports = exports; - -new Function("module", transpiled)(context.module); - -console.log("-- EVALUATION RESULT:"); - -// Warm-up for hot VM performance testing -// for (let i = 0; i < 10000; i++) { -// context.module.exports(api); -// } - -console.log(measureTime(() => context.module.exports(api))); +console.log(evaluate(nixFile)); diff --git a/src/astformer/index.js b/src/astformer/index.js index c638471..853dcd0 100644 --- a/src/astformer/index.js +++ b/src/astformer/index.js @@ -196,7 +196,7 @@ module.exports = function optimizeTree(ast, optimizers) { debuggers[debuggerName](`Node of type '${typeOf(node)}' replaced by node of type '${typeOf(result)}'`); if (iterations >= EVALUATION_LIMIT) { - throw new Error(`Exceeded evaluation limit in optimizer ${debuggerName}; aborting optimization. If you are a user of raqb, please report this as a bug. If you are writing an optimizer, make sure that your optimizer eventually stabilizes on a terminal condition (ie. NoChange)!`); + throw new Error(`Exceeded evaluation limit in optimizer ${debuggerName}; aborting optimization. If you are a user of this software, please report this as a bug. If you are a developer writing an optimizer, make sure that your optimizer eventually stabilizes on a terminal condition (ie. NoChange)!`); } else { return handleASTNode(result, iterations + 1, path, initialStateLog, context); } diff --git a/src/evaluate.js b/src/evaluate.js new file mode 100644 index 0000000..c006ca9 --- /dev/null +++ b/src/evaluate.js @@ -0,0 +1,45 @@ +"use strict"; + +const measureTime = require("./astformer/util/measure-time"); +const transpile = require("./transpile"); + +module.exports = function evaluate(nixCode) { + let transpiled = transpile(nixCode); + + const api = { + builtins: {}, + $$jsNix$memoize: function (func) { + let isCalled = false; + let storedResult; + + return function (arg) { + if (isCalled === false) { + storedResult = func(arg); + isCalled = true; + } + + return storedResult; + }; + } + }; + + // TODO: Switch to Node `vm` API instead, and check whether there's a polyfill for it for non-Node environments, build a custom one if not + const context = { module: {}, exports: {} }; + context.module.exports = exports; + + new Function("module", transpiled)(context.module); + + // Warm-up for hot VM performance testing + // for (let i = 0; i < 10000; i++) { + // context.module.exports(api); + // } + + let result = measureTime(() => context.module.exports(api)); + + if (process.env.DEBUG_NIX) { + console.log("-- EVALUATION RESULT:"); + console.log(result); + } + + return result; +}; diff --git a/src/transformers/literals.js b/src/transformers/literals.js index de99457..6453085 100644 --- a/src/transformers/literals.js +++ b/src/transformers/literals.js @@ -7,5 +7,13 @@ module.exports = { visitors: { NixIntegerLiteral: (node) => types.numericLiteral(parseInt(node.value)), NixFloatLiteral: (node) => types.numericLiteral(parseFloat(node.value)), + NixStringLiteral: (node) => { + if (node.parts.length === 1 && node.parts[0].type === "NixInterpolationLiteral") { + // Fast case; just a simple, non-interpolated string literal + return types.stringLiteral(node.parts[0].value); + } else { + throw new Error(`Unimplemented: string interpolation is not supported yet`); + } + } } }; diff --git a/tests/upstream-nix.js b/tests/upstream-nix.js new file mode 100644 index 0000000..c438f5a --- /dev/null +++ b/tests/upstream-nix.js @@ -0,0 +1,40 @@ +"use strict"; + +const tape = require("tape-catch"); +const fs = require("fs"); +const path = require("path"); +const evaluate = require("../src/evaluate"); + +const NIX_SOURCE_REPO = process.env.NIX_SOURCE_REPO; + +if (NIX_SOURCE_REPO == null) { + throw new Error(`To run the upstream Nix language tests, you must specify a NIX_SOURCE_REPO environment variable, that points at the root of a local checkout of the Git repository for Nix`); +} + +const testsPath = path.join(NIX_SOURCE_REPO, "tests/lang"); + +let tests = fs.readdirSync(testsPath) + .filter((entry) => entry.endsWith(".exp")) + .map((entry) => entry.replace(/\.exp$/, "")); + +for (let test of tests) { + try { + let expression = fs.readFileSync(path.join(testsPath, `${test}.nix`), "utf8"); + let expectedResult = fs.readFileSync(path.join(testsPath, `${test}.exp`), "utf8").replace(/\n$/, ""); + + tape(`Nix upstream language tests - ${test}`, (test) => { + test.plan(1); + + let result = evaluate(expression).value.toString(); + + test.equals(expectedResult, result); + }); + } catch (error) { + // FIXME: This would currently cause ENOENTs during evaluation (eg. reading a file from Nix itself) to be ignored + if (error.code === "ENOENT") { + // skip + } else { + throw error; + } + } +}