Skip to content

Commit 96b62ef

Browse files
Jiamin ShenTaraxacummartin-henz
authored
New test script using JavaScript (source-academy#444)
* test: new test script using js * yarn test now runs the new way * removing that shell script Co-authored-by: Taraxacum <john980118@sjtu.edu.cn> Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>
1 parent 583a212 commit 96b62ef

File tree

3 files changed

+162
-79
lines changed

3 files changed

+162
-79
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"split": "yarn process web split",
4545
"svgpdf": "./scripts/svg_to_pdf.sh",
4646
"staging": "yarn do staging",
47-
"test": "./scripts/test.sh",
47+
"test": "node ./scripts/test.js",
4848
"try": "cd docs_out; http-server --port 8080",
4949
"via": "yarn do via",
5050
"web": "yarn process web"

scripts/test.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
const { createContext, runInContext, parseError } = require("js-slang");
2+
const { exit } = require("process");
3+
const { readdirSync, readFileSync, statSync } = require("fs");
4+
const { sourceLanguages } = require("js-slang/dist/constants");
5+
const colors = require("colors/safe");
6+
const Path = require("path");
7+
8+
const DEFAULT_SOURCE_FOLDER = "js_programs";
9+
const DEFAULT_CHAPTER = 4;
10+
const DEFAULT_VARIANT = "default";
11+
let count_pass = 0;
12+
let count_fail = 0;
13+
14+
/**
15+
* Returns true iff the given chapter and variant combination is supported.
16+
* https://github.com/source-academy/js-slang/blob/master/src/repl/repl.ts#L61-L76
17+
*/
18+
function validChapterVariant(chapter, variant) {
19+
if (variant === "interpreter") {
20+
return true;
21+
}
22+
if (variant === "substituter" && (chapter === 1 || chapter === 2)) {
23+
return true;
24+
}
25+
for (const lang of sourceLanguages) {
26+
if (lang.chapter === chapter && lang.variant === variant) return true;
27+
}
28+
return false;
29+
}
30+
31+
/**
32+
* Source Interpreter
33+
* https://github.com/source-academy/js-slang/blob/master/src/repl/repl.ts#L95-L110
34+
*/
35+
async function interpret(code, chapter, variant) {
36+
if (!validChapterVariant(chapter, variant)) {
37+
console.log(
38+
colors.red(
39+
`The chapter and variant combination (${chapter}, ${variant}) provided is unsupported.`
40+
)
41+
);
42+
}
43+
44+
const context = createContext(chapter, variant, undefined, undefined);
45+
46+
const options = {
47+
scheduler: "preemptive",
48+
executionMethod: ["interpreter", "non-det"].includes(variant)
49+
? "interpreter"
50+
: "native",
51+
variant: variant,
52+
useSubst: variant === "substituter"
53+
};
54+
55+
return runInContext(code, context, options).then(preludeResult => {
56+
if (["finished", "suspended-non-det"].includes(preludeResult.status)) {
57+
return preludeResult.value;
58+
} else {
59+
throw context.errors;
60+
}
61+
});
62+
}
63+
64+
async function test(test_name, chapter, variant, source_code, expected_output) {
65+
console.log(`${test_name}, expecting: ${expected_output}`);
66+
67+
try {
68+
const test_output = await interpret(source_code, chapter, variant);
69+
70+
let pass = false;
71+
if (typeof test_output != "string") {
72+
try {
73+
const expected = JSON.stringify(eval(expected_output));
74+
const output = JSON.stringify(test_output);
75+
pass = expected == output;
76+
} catch (error) {
77+
pass = false;
78+
}
79+
} else {
80+
pass =
81+
expected_output == test_output || expected_output == `'${test_output}'`;
82+
}
83+
84+
if (pass) {
85+
console.log(colors.green("PASS"));
86+
count_pass++;
87+
} else {
88+
console.log(colors.red(`FAIL:`));
89+
console.log(typeof test_output);
90+
for (let line of `${test_output}`.split("\n")) {
91+
console.log(colors.red(`> ${line}`));
92+
}
93+
console.log(colors.red(`---`));
94+
for (let line of expected_output.split("\n")) {
95+
console.log(colors.red(`< ${line}`));
96+
}
97+
count_fail++;
98+
}
99+
} catch (error) {
100+
try {
101+
console.log(colors.red(parseError(error)));
102+
count_fail++;
103+
} catch {
104+
console.error(colors.red(error));
105+
exit(-1);
106+
}
107+
}
108+
}
109+
110+
async function test_source(file_path) {
111+
const source_code = readFileSync(file_path, { encoding: "utf-8" }).trim();
112+
113+
const lines = source_code.split("\n");
114+
115+
// find the expected output
116+
let mobj = /\/\/ expected:\s*(.*)/.exec(lines[lines.length - 1]);
117+
if (!mobj) {
118+
return;
119+
}
120+
const expected_output = mobj[1];
121+
122+
// handle chapter and variant
123+
let chapter = DEFAULT_CHAPTER;
124+
let variant = DEFAULT_VARIANT;
125+
if ((mobj = /chapter=\s*(\d+)/.exec(lines[0]))) {
126+
chapter = parseInt(mobj[1]);
127+
}
128+
if ((mobj = /variant=\s*(\S+)/.exec(lines[0]))) {
129+
variant = mobj[1];
130+
}
131+
132+
return await test(file_path, chapter, variant, source_code, expected_output);
133+
}
134+
135+
async function test_root(path) {
136+
const stat = statSync(path);
137+
if (stat.isDirectory(path)) {
138+
for (let sub of readdirSync(path)) {
139+
await test_root(Path.resolve(path, sub));
140+
}
141+
} else if (stat.isFile()) {
142+
await test_source(Path.relative(".", path));
143+
}
144+
}
145+
146+
(async () => {
147+
const opt = require("node-getopt")
148+
.create([])
149+
.setHelp("Usage: yarn test [source folder]")
150+
.parseSystem();
151+
152+
let source_folder = DEFAULT_SOURCE_FOLDER;
153+
if (opt.argv.length > 0) {
154+
source_folder = Path.resolve(...opt.argv);
155+
}
156+
157+
await test_root(source_folder);
158+
159+
console.log(`${count_pass + count_fail} test cases completed.`);
160+
console.log(`${count_pass} passed, ${count_fail} failed`);
161+
})();

scripts/test.sh

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)