@pleger/esa-js 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GITHUB_USAGE.md +121 -0
- package/LICENSE +21 -0
- package/NPM_PUBLISH.md +51 -0
- package/PATTERNS.md +85 -0
- package/README.md +160 -0
- package/ROADMAP_10_STEPS.md +14 -0
- package/STEP1_TRIAGE.md +139 -0
- package/aspectscript-cli.js +61 -0
- package/aspectscript.js +1227 -0
- package/conformance/01-noBR-base-reentrancy.js +15 -0
- package/conformance/02-level-sensitive-cflow.js +19 -0
- package/conformance/03-scoping-strategy-d.js +20 -0
- package/conformance/04-jp-isolation-before-order.js +17 -0
- package/esa.js +904 -0
- package/index.d.ts +123 -0
- package/instrument.js +668 -0
- package/package.json +68 -0
- package/run-conformance.js +51 -0
- package/run-script.js +193 -0
- package/run-tests.js +236 -0
- package/transform-cache.js +54 -0
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pleger/esa-js",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "ESA-JS and AspectScript runtime, instrumentation, and CLI tooling",
|
|
5
|
+
"main": "aspectscript.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/pleger/ESA.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/pleger/ESA#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/pleger/ESA/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"aspect-oriented-programming",
|
|
19
|
+
"esa",
|
|
20
|
+
"stateful-aspects",
|
|
21
|
+
"tracematch",
|
|
22
|
+
"aspectscript",
|
|
23
|
+
"javascript",
|
|
24
|
+
"typescript",
|
|
25
|
+
"aop",
|
|
26
|
+
"join-point"
|
|
27
|
+
],
|
|
28
|
+
"files": [
|
|
29
|
+
"aspectscript.js",
|
|
30
|
+
"esa.js",
|
|
31
|
+
"aspectscript-cli.js",
|
|
32
|
+
"instrument.js",
|
|
33
|
+
"transform-cache.js",
|
|
34
|
+
"run-script.js",
|
|
35
|
+
"run-tests.js",
|
|
36
|
+
"run-conformance.js",
|
|
37
|
+
"index.d.ts",
|
|
38
|
+
"conformance/",
|
|
39
|
+
"README.md",
|
|
40
|
+
"GITHUB_USAGE.md",
|
|
41
|
+
"PATTERNS.md",
|
|
42
|
+
"ROADMAP_10_STEPS.md",
|
|
43
|
+
"NPM_PUBLISH.md",
|
|
44
|
+
"STEP1_TRIAGE.md"
|
|
45
|
+
],
|
|
46
|
+
"exports": {
|
|
47
|
+
".": "./aspectscript.js",
|
|
48
|
+
"./esa": "./esa.js",
|
|
49
|
+
"./instrument": "./instrument.js"
|
|
50
|
+
},
|
|
51
|
+
"bin": {
|
|
52
|
+
"aspectscript": "aspectscript-cli.js",
|
|
53
|
+
"esa": "aspectscript-cli.js"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "node run-tests.js",
|
|
57
|
+
"test:failed": "node run-tests.js --failed",
|
|
58
|
+
"test:conformance": "node run-conformance.js",
|
|
59
|
+
"run:script": "node run-script.js",
|
|
60
|
+
"cli": "node aspectscript-cli.js",
|
|
61
|
+
"pack:check": "npm_config_cache=/tmp/aspectscript-npm-cache npm pack --dry-run"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"acorn": "^8.16.0",
|
|
65
|
+
"acorn-walk": "^8.3.5",
|
|
66
|
+
"astring": "^1.9.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
function listConformanceFiles(root, filters) {
|
|
8
|
+
const files = fs.readdirSync(root)
|
|
9
|
+
.filter((name) => name.endsWith(".js"))
|
|
10
|
+
.sort();
|
|
11
|
+
if (!filters.length) {
|
|
12
|
+
return files;
|
|
13
|
+
}
|
|
14
|
+
return files.filter((name) => filters.some((filter) => name.includes(filter)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function main() {
|
|
18
|
+
const conformanceDir = path.join(__dirname, "conformance");
|
|
19
|
+
const filters = process.argv.slice(2);
|
|
20
|
+
const files = listConformanceFiles(conformanceDir, filters);
|
|
21
|
+
if (!files.length) {
|
|
22
|
+
process.stdout.write("No conformance files matched.\n");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let failures = 0;
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const fullPath = path.join(conformanceDir, file);
|
|
29
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, "run-script.js"), fullPath], {
|
|
30
|
+
cwd: __dirname,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
});
|
|
33
|
+
if (result.status !== 0) {
|
|
34
|
+
failures += 1;
|
|
35
|
+
process.stdout.write(file + "\n");
|
|
36
|
+
if (result.stdout) {
|
|
37
|
+
process.stdout.write(result.stdout);
|
|
38
|
+
}
|
|
39
|
+
if (result.stderr) {
|
|
40
|
+
process.stdout.write(result.stderr);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process.stdout.write("Evaluated " + files.length + " conformance file(s). Failed " + failures + ".\n");
|
|
46
|
+
if (failures) {
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main();
|
package/run-script.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const vm = require("vm");
|
|
6
|
+
|
|
7
|
+
const { transformProgramCached } = require("./transform-cache");
|
|
8
|
+
const { createAspectScript } = require("./aspectscript");
|
|
9
|
+
|
|
10
|
+
function stripLoads(source) {
|
|
11
|
+
return source
|
|
12
|
+
.split("\n")
|
|
13
|
+
.filter((line) => !/^\s*load\(/.test(line))
|
|
14
|
+
.join("\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeTesting(context) {
|
|
18
|
+
const flags = [];
|
|
19
|
+
|
|
20
|
+
function stringify(value) {
|
|
21
|
+
if (value && (typeof value === "object" || typeof value === "function") &&
|
|
22
|
+
typeof value.toString === "function" &&
|
|
23
|
+
value.toString !== Object.prototype.toString) {
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function fail(message) {
|
|
30
|
+
throw new Error(message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
flag(value) {
|
|
35
|
+
flags.push(stringify(value));
|
|
36
|
+
},
|
|
37
|
+
assert(expr) {
|
|
38
|
+
if (!vm.runInContext(String(expr), context)) {
|
|
39
|
+
fail("Assertion failed: " + expr);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
assert2(label, condition) {
|
|
43
|
+
if (!condition) {
|
|
44
|
+
fail("Assertion " + label + " failed");
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
check(...expected) {
|
|
48
|
+
if (expected.length === 1 &&
|
|
49
|
+
typeof expected[0] === "string" &&
|
|
50
|
+
/\s/.test(expected[0]) &&
|
|
51
|
+
!/^\[.*\]$/.test(expected[0])) {
|
|
52
|
+
if (flags.length === 0) {
|
|
53
|
+
fail(expected[0]);
|
|
54
|
+
}
|
|
55
|
+
flags.length = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (expected.length !== flags.length) {
|
|
59
|
+
fail("Expected: " + expected.join(" ") + " | Actual: " + flags.join(" "));
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < expected.length; i += 1) {
|
|
62
|
+
if (expected[i] !== flags[i]) {
|
|
63
|
+
fail("Expected: " + expected.join(" ") + " | Actual: " + flags.join(" "));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
flags.length = 0;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createContext() {
|
|
72
|
+
const sandbox = {
|
|
73
|
+
console,
|
|
74
|
+
require,
|
|
75
|
+
print: (...args) => console.log(...args),
|
|
76
|
+
load: () => {},
|
|
77
|
+
globalThis: null,
|
|
78
|
+
setTimeout,
|
|
79
|
+
clearTimeout,
|
|
80
|
+
setInterval,
|
|
81
|
+
clearInterval,
|
|
82
|
+
Date,
|
|
83
|
+
Math,
|
|
84
|
+
Array,
|
|
85
|
+
Object,
|
|
86
|
+
Function,
|
|
87
|
+
String,
|
|
88
|
+
Number,
|
|
89
|
+
Boolean,
|
|
90
|
+
RegExp,
|
|
91
|
+
Error,
|
|
92
|
+
TypeError,
|
|
93
|
+
JSON,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const context = vm.createContext({ ...sandbox });
|
|
97
|
+
context.globalThis = context;
|
|
98
|
+
context.Function = function ScopedFunction(...parts) {
|
|
99
|
+
const body = parts.length ? String(parts.pop()) : "";
|
|
100
|
+
const params = parts.map(String).join(",");
|
|
101
|
+
return vm.runInContext("(function(" + params + "){" + body + "\n})", context);
|
|
102
|
+
};
|
|
103
|
+
context.Function.prototype = Function.prototype;
|
|
104
|
+
|
|
105
|
+
const AspectScript = createAspectScript(context);
|
|
106
|
+
context.AspectScript = AspectScript;
|
|
107
|
+
context.AJS = AspectScript;
|
|
108
|
+
context.PCs = AspectScript.Pointcuts;
|
|
109
|
+
context.Testing = makeTesting(context);
|
|
110
|
+
return context;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function runFile(filePath, options) {
|
|
114
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
115
|
+
const transformed = transformProgramCached(stripLoads(source), {
|
|
116
|
+
cacheEnabled: options && options.cacheEnabled !== false,
|
|
117
|
+
namespace: "scripts",
|
|
118
|
+
}).code;
|
|
119
|
+
const context = createContext();
|
|
120
|
+
if (options && options.traceEnabled) {
|
|
121
|
+
context.AspectScript.tracer.enable();
|
|
122
|
+
}
|
|
123
|
+
vm.runInContext(transformed, context, { filename: filePath });
|
|
124
|
+
return context;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseArgs(argv) {
|
|
128
|
+
let target = null;
|
|
129
|
+
let traceJsonPath = null;
|
|
130
|
+
let cacheEnabled = true;
|
|
131
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
132
|
+
const arg = argv[i];
|
|
133
|
+
if (arg === "--trace-json") {
|
|
134
|
+
traceJsonPath = argv[i + 1] || null;
|
|
135
|
+
i += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (arg === "--no-cache") {
|
|
139
|
+
cacheEnabled = false;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!target) {
|
|
143
|
+
target = arg;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { target, traceJsonPath, cacheEnabled };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function main() {
|
|
150
|
+
const { target, traceJsonPath, cacheEnabled } = parseArgs(process.argv.slice(2));
|
|
151
|
+
if (!target) {
|
|
152
|
+
process.stderr.write("Usage: node run-script.js <path/to/file.js> [--trace-json trace.json] [--no-cache]\n");
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (process.argv.includes("--trace-json") && !traceJsonPath) {
|
|
157
|
+
process.stderr.write("Missing value for --trace-json\n");
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const fullPath = path.resolve(process.cwd(), target);
|
|
163
|
+
if (!fs.existsSync(fullPath)) {
|
|
164
|
+
process.stderr.write("File not found: " + fullPath + "\n");
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let context = null;
|
|
170
|
+
if (traceJsonPath) {
|
|
171
|
+
const traceOut = path.resolve(process.cwd(), traceJsonPath);
|
|
172
|
+
process.stdout.write("Trace output: " + traceOut + "\n");
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
context = runFile(fullPath, {
|
|
176
|
+
traceEnabled: Boolean(traceJsonPath),
|
|
177
|
+
cacheEnabled,
|
|
178
|
+
});
|
|
179
|
+
if (traceJsonPath) {
|
|
180
|
+
const traceOut = path.resolve(process.cwd(), traceJsonPath);
|
|
181
|
+
context.AspectScript.tracer.saveToFile(traceOut, true);
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (traceJsonPath && context) {
|
|
185
|
+
const traceOut = path.resolve(process.cwd(), traceJsonPath);
|
|
186
|
+
context.AspectScript.tracer.saveToFile(traceOut, true);
|
|
187
|
+
}
|
|
188
|
+
process.stderr.write(String(error && error.stack ? error.stack : error) + "\n");
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main();
|
package/run-tests.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const vm = require("vm");
|
|
6
|
+
|
|
7
|
+
const { transformProgramCached } = require("./transform-cache");
|
|
8
|
+
const LAST_FAILS_FILE = path.join(__dirname, "tests", "lastFails.txt");
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const filters = [];
|
|
12
|
+
let failedOnly = false;
|
|
13
|
+
let cacheEnabled = true;
|
|
14
|
+
let showCacheStats = false;
|
|
15
|
+
for (const arg of argv) {
|
|
16
|
+
if (arg === "-f" || arg === "--failed") {
|
|
17
|
+
failedOnly = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (arg === "--no-cache") {
|
|
21
|
+
cacheEnabled = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg === "--cache-stats") {
|
|
25
|
+
showCacheStats = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
filters.push(arg);
|
|
29
|
+
}
|
|
30
|
+
return { failedOnly, filters, cacheEnabled, showCacheStats };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function listTests(root, filters, failedOnly) {
|
|
34
|
+
if (failedOnly) {
|
|
35
|
+
if (!fs.existsSync(LAST_FAILS_FILE)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const previousFailures = fs.readFileSync(LAST_FAILS_FILE, "utf8")
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.filter((name) => /^test.*\.js$/.test(name))
|
|
43
|
+
.filter((name) => fs.existsSync(path.join(root, name)));
|
|
44
|
+
if (!filters.length) {
|
|
45
|
+
return previousFailures;
|
|
46
|
+
}
|
|
47
|
+
return previousFailures.filter((name) => filters.some((filter) => name.startsWith(filter)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const files = fs.readdirSync(root)
|
|
51
|
+
.filter((name) => /^test.*\.js$/.test(name))
|
|
52
|
+
.sort();
|
|
53
|
+
if (!filters.length) {
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
return files.filter((name) => filters.some((filter) => name.startsWith(filter)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function stripLoads(source) {
|
|
60
|
+
return source
|
|
61
|
+
.split("\n")
|
|
62
|
+
.filter((line) => !/^\s*load\(/.test(line))
|
|
63
|
+
.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeTesting(context) {
|
|
67
|
+
const flags = [];
|
|
68
|
+
|
|
69
|
+
function stringify(value) {
|
|
70
|
+
if (value && (typeof value === "object" || typeof value === "function") &&
|
|
71
|
+
typeof value.toString === "function" &&
|
|
72
|
+
value.toString !== Object.prototype.toString) {
|
|
73
|
+
return String(value);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fail(message) {
|
|
79
|
+
throw new Error(message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
flag(value) {
|
|
84
|
+
flags.push(stringify(value));
|
|
85
|
+
},
|
|
86
|
+
assert(expr) {
|
|
87
|
+
if (!vm.runInContext(String(expr), context)) {
|
|
88
|
+
fail("Assertion failed: " + expr);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
assert2(label, condition) {
|
|
92
|
+
if (!condition) {
|
|
93
|
+
fail("Assertion " + label + " failed");
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
check(...expected) {
|
|
97
|
+
if (expected.length === 1 &&
|
|
98
|
+
typeof expected[0] === "string" &&
|
|
99
|
+
/\s/.test(expected[0]) &&
|
|
100
|
+
!/^\[.*\]$/.test(expected[0])) {
|
|
101
|
+
if (flags.length === 0) {
|
|
102
|
+
fail(expected[0]);
|
|
103
|
+
}
|
|
104
|
+
flags.length = 0;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (expected.length !== flags.length) {
|
|
108
|
+
fail("Expected: " + expected.join(" ") + " | Actual: " + flags.join(" "));
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < expected.length; i += 1) {
|
|
111
|
+
if (expected[i] !== flags[i]) {
|
|
112
|
+
fail("Expected: " + expected.join(" ") + " | Actual: " + flags.join(" "));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
flags.length = 0;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function runTest(filePath, options) {
|
|
121
|
+
delete require.cache[require.resolve("./aspectscript")];
|
|
122
|
+
const { createAspectScript } = require("./aspectscript");
|
|
123
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
124
|
+
const transformResult = transformProgramCached(stripLoads(source), {
|
|
125
|
+
cacheEnabled: options.cacheEnabled,
|
|
126
|
+
namespace: "tests",
|
|
127
|
+
});
|
|
128
|
+
const transformed = transformResult.code;
|
|
129
|
+
const sandbox = {
|
|
130
|
+
console,
|
|
131
|
+
require,
|
|
132
|
+
print: () => {},
|
|
133
|
+
load: () => {},
|
|
134
|
+
globalThis: null,
|
|
135
|
+
setTimeout,
|
|
136
|
+
clearTimeout,
|
|
137
|
+
setInterval,
|
|
138
|
+
clearInterval,
|
|
139
|
+
Date,
|
|
140
|
+
Math,
|
|
141
|
+
Array,
|
|
142
|
+
Object,
|
|
143
|
+
Function,
|
|
144
|
+
String,
|
|
145
|
+
Number,
|
|
146
|
+
Boolean,
|
|
147
|
+
RegExp,
|
|
148
|
+
Error,
|
|
149
|
+
TypeError,
|
|
150
|
+
JSON,
|
|
151
|
+
};
|
|
152
|
+
const context = vm.createContext({
|
|
153
|
+
...sandbox,
|
|
154
|
+
});
|
|
155
|
+
context.globalThis = context;
|
|
156
|
+
context.Function = function ScopedFunction(...parts) {
|
|
157
|
+
const body = parts.length ? String(parts.pop()) : "";
|
|
158
|
+
const params = parts.map(String).join(",");
|
|
159
|
+
return vm.runInContext("(function(" + params + "){" + body + "\n})", context);
|
|
160
|
+
};
|
|
161
|
+
context.Function.prototype = Function.prototype;
|
|
162
|
+
const AspectScript = createAspectScript(context);
|
|
163
|
+
context.AspectScript = AspectScript;
|
|
164
|
+
context.AJS = AspectScript;
|
|
165
|
+
context.PCs = AspectScript.Pointcuts;
|
|
166
|
+
context.Testing = makeTesting(context);
|
|
167
|
+
try {
|
|
168
|
+
vm.runInContext(transformed, context, { filename: filePath });
|
|
169
|
+
return { error: null, fromCache: transformResult.fromCache };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return { error, fromCache: transformResult.fromCache };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function summarizeError(error) {
|
|
176
|
+
const stack = String(error && error.stack ? error.stack : error || "");
|
|
177
|
+
const lines = stack.split("\n");
|
|
178
|
+
const message = lines[0] || "Error";
|
|
179
|
+
const topFrame = lines.find((line) =>
|
|
180
|
+
/:\d+:\d+\)?$/.test(line) &&
|
|
181
|
+
!line.includes("run-tests.js") &&
|
|
182
|
+
!line.includes("node:vm")) || "";
|
|
183
|
+
return { message, topFrame, stack };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function writeLastFailures(failedNames) {
|
|
187
|
+
const payload = failedNames.length ? failedNames.join("\n") + "\n" : "";
|
|
188
|
+
fs.writeFileSync(LAST_FAILS_FILE, payload, "utf8");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function main() {
|
|
192
|
+
const testDir = path.join(__dirname, "tests");
|
|
193
|
+
const { failedOnly, filters, cacheEnabled, showCacheStats } = parseArgs(process.argv.slice(2));
|
|
194
|
+
const tests = listTests(testDir, filters, failedOnly);
|
|
195
|
+
if (!tests.length) {
|
|
196
|
+
if (failedOnly) {
|
|
197
|
+
process.stdout.write("No failed tests recorded in tests/lastFails.txt.\n");
|
|
198
|
+
} else {
|
|
199
|
+
process.stdout.write("No tests matched the provided filters.\n");
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const failedNames = [];
|
|
205
|
+
let failures = 0;
|
|
206
|
+
let cacheHits = 0;
|
|
207
|
+
for (const name of tests) {
|
|
208
|
+
const filePath = path.join(testDir, name);
|
|
209
|
+
const result = runTest(filePath, { cacheEnabled });
|
|
210
|
+
const error = result.error;
|
|
211
|
+
if (result.fromCache) {
|
|
212
|
+
cacheHits += 1;
|
|
213
|
+
}
|
|
214
|
+
if (error) {
|
|
215
|
+
failures += 1;
|
|
216
|
+
failedNames.push(name);
|
|
217
|
+
const details = summarizeError(error);
|
|
218
|
+
process.stdout.write(name + "\n");
|
|
219
|
+
process.stdout.write("Summary: " + details.message + "\n");
|
|
220
|
+
if (details.topFrame) {
|
|
221
|
+
process.stdout.write("Top frame: " + details.topFrame.trim() + "\n");
|
|
222
|
+
}
|
|
223
|
+
process.stdout.write(details.stack + "\n");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
writeLastFailures(failedNames);
|
|
227
|
+
process.stdout.write("Evaluated " + tests.length + " test(s). Failed " + failures + ".\n");
|
|
228
|
+
if (showCacheStats) {
|
|
229
|
+
process.stdout.write("Cache hits: " + cacheHits + "/" + tests.length + ".\n");
|
|
230
|
+
}
|
|
231
|
+
if (failures) {
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const { transformProgram } = require("./instrument");
|
|
6
|
+
|
|
7
|
+
const CACHE_DIR = path.join(__dirname, ".aspectscript-cache");
|
|
8
|
+
const instrumentSource = fs.readFileSync(path.join(__dirname, "instrument.js"), "utf8");
|
|
9
|
+
const instrumentHash = crypto.createHash("sha1").update(instrumentSource).digest("hex");
|
|
10
|
+
|
|
11
|
+
function cacheKey(source, namespace) {
|
|
12
|
+
return crypto
|
|
13
|
+
.createHash("sha1")
|
|
14
|
+
.update("aspectscript-transform-v1")
|
|
15
|
+
.update("\n")
|
|
16
|
+
.update(instrumentHash)
|
|
17
|
+
.update("\n")
|
|
18
|
+
.update(namespace || "default")
|
|
19
|
+
.update("\n")
|
|
20
|
+
.update(source)
|
|
21
|
+
.digest("hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureCacheDir() {
|
|
25
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
26
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function transformProgramCached(source, options) {
|
|
31
|
+
const opts = options || {};
|
|
32
|
+
const cacheEnabled = opts.cacheEnabled !== false;
|
|
33
|
+
const namespace = opts.namespace || "default";
|
|
34
|
+
|
|
35
|
+
if (!cacheEnabled) {
|
|
36
|
+
return { code: transformProgram(source), fromCache: false };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ensureCacheDir();
|
|
40
|
+
const key = cacheKey(source, namespace);
|
|
41
|
+
const filePath = path.join(CACHE_DIR, namespace + "-" + key + ".js");
|
|
42
|
+
if (fs.existsSync(filePath)) {
|
|
43
|
+
return { code: fs.readFileSync(filePath, "utf8"), fromCache: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const transformed = transformProgram(source);
|
|
47
|
+
fs.writeFileSync(filePath, transformed, "utf8");
|
|
48
|
+
return { code: transformed, fromCache: false };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
CACHE_DIR,
|
|
53
|
+
transformProgramCached,
|
|
54
|
+
};
|