批量修改
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Regression tests for the editor's real interpreter and output callback.
|
||||
// Regression tests for variable scopes in the editor interpreter.
|
||||
// Run: node scripts/test-cpp-regressions.cjs
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
@@ -32,19 +32,13 @@ int main() {
|
||||
}
|
||||
return 0;
|
||||
}`;
|
||||
// The third screenshot reports cout without supplying a cout example.
|
||||
// Add observable output to the supplied program to check both fixes together.
|
||||
const customerEmailWithOutput = customerEmailCode.replace(' playerMove(i);',
|
||||
' cout << "i=" << i << endl;\n playerMove(i);');
|
||||
|
||||
async function run(code, expected, configure) {
|
||||
const moves = [];
|
||||
const turns = [];
|
||||
const output = [];
|
||||
context.window = {isScriptRunOver: false, cppGameAPI: {
|
||||
playerMove: async n => moves.push(n),
|
||||
turnLeft: async n => turns.push(n),
|
||||
cmdPrint: text => output.push(text)
|
||||
cmdPrint: () => {}
|
||||
}};
|
||||
const executor = new CppCodeExecutor(code, null);
|
||||
if (configure) configure(executor, context.window);
|
||||
@@ -53,7 +47,6 @@ async function run(code, expected, configure) {
|
||||
await executor.run();
|
||||
if (expected.moves) assert.deepStrictEqual(moves, expected.moves);
|
||||
if (expected.turns) assert.deepStrictEqual(turns, expected.turns);
|
||||
if (expected.output) assert.deepStrictEqual(output, expected.output);
|
||||
assert.strictEqual(executor.variables, executor.globalVariables, 'scope must unwind after execution');
|
||||
assert.strictEqual(executor.callStack.length, 0);
|
||||
return executor;
|
||||
@@ -61,12 +54,10 @@ async function run(code, expected, configure) {
|
||||
|
||||
const cases = [
|
||||
['customer email complete program (screenshots 1 and 2)', customerEmailCode, {moves: [0], turns: [-1]}],
|
||||
['customer email with variable output (screenshot 3)', customerEmailWithOutput, {moves: [0], turns: [-1], output: ['i=0\n']}],
|
||||
['nested loops', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}],
|
||||
['loop initializer restores shadowed value', main('int i = 9;\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [9]}],
|
||||
['loop assignment updates existing value', main('int i = 9;\nfor (i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [2]}],
|
||||
['block declaration versus assignment', main('int n = 1;\nif (true) {\nint n = 7;\nplayerMove(n);\n}\nif (true) {\nn = 3;\n}\nplayerMove(n);'), {moves: [7, 3]}],
|
||||
['string block shadowing', main('string msg = "outer";\nif (true) {\nstring msg = "C++";\ncout << msg;\n}\ncout << msg;'), {output: ['C++', 'outer']}],
|
||||
['for body scope per iteration', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nplayerMove(x);\nint x = 2;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
|
||||
['while body scope per iteration', main('int x = 9;\nint i = 0;\nwhile (i < 2) {\nplayerMove(x);\nint x = 2;\ni++;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
|
||||
['function locals', 'void f() {\nint n = 7;\nplayerMove(n);\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [7, 2]}],
|
||||
@@ -75,14 +66,6 @@ const cases = [
|
||||
['early void return unwinds scope', 'void f() {\nint n = 8;\nreturn;\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [2]}],
|
||||
['continue unwinds body scope', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nint x = 2;\ncontinue;\n}\nplayerMove(x);'), {moves: [9]}],
|
||||
['break does not increment', main('int i = 0;\nfor (i = 0; i < 2; i++) {\nbreak;\n}\nplayerMove(i);'), {moves: [0]}],
|
||||
['cout with equals', main('int i = 7;\ncout << "i=" << i << endl;'), {output: ['i=7\n']}],
|
||||
['cout with operator text', main('cout << "value=42 C++ -- +=" << endl;'), {output: ['value=42 C++ -- +=\n']}],
|
||||
['cout with game API text', main('cout << "playerMove(2)" << endl;'), {moves: [], output: ['playerMove(2)\n']}],
|
||||
['qualified cout', main('std::cout << "Hello" << std::endl;'), {output: ['Hello\n']}],
|
||||
['cout postfix and prefix', main('int i = 0;\ncout << i++ << endl;\ncout << ++i << endl;\ncout << i-- << endl;\ncout << --i << endl;'), {output: ['0\n', '2\n', '2\n', '0\n']}],
|
||||
['cout arithmetic', main('int i = 3;\ncout << "result=" << (i * 2 + 1) << endl;'), {output: ['result=7\n']}],
|
||||
['escaped quotes and stream delimiters', main(String.raw`cout << "a\"<<b\nC++" << endl;`), {output: ['a"<<b\nC++\n']}],
|
||||
['consecutive output fragments', main('cout << "A";\ncout << "B" << endl;'), {output: ['A', 'B\n']}]
|
||||
];
|
||||
|
||||
(async () => {
|
||||
@@ -90,6 +73,15 @@ const cases = [
|
||||
try { await run(code, expected); } catch (error) {error.message = `${name}: ${error.message}`; throw error;}
|
||||
console.log(`PASS ${name}`);
|
||||
}
|
||||
const strings = new CppCodeExecutor('', null);
|
||||
await strings.executeSimpleStatement('string msg = "outer";');
|
||||
await strings.withVariableScope(async () => {
|
||||
await strings.executeSimpleStatement('string msg = "inner";');
|
||||
assert.strictEqual(strings.variables.get('msg'), 'inner');
|
||||
});
|
||||
assert.strictEqual(strings.variables.get('msg'), 'outer');
|
||||
console.log('PASS string block shadowing');
|
||||
|
||||
// Seed the interpreter's global environment to verify lexical lookup and writes.
|
||||
const globalExecutor = await run('void f() {\ng += 1;\nplayerMove(g);\n}\n' + main('int g = 9;\nf();\nplayerMove(g);'), {moves: [2, 9]}, executor => executor.variables.set('g', 1));
|
||||
assert.strictEqual(globalExecutor.variables.get('g'), 2);
|
||||
@@ -112,51 +104,5 @@ const cases = [
|
||||
console.log(`PASS scope cleanup on ${stop ? 'stop' : 'error'}`);
|
||||
}
|
||||
|
||||
// Extract the actual React component callback, avoiding a game-engine dependency.
|
||||
const uiPath = path.resolve(__dirname, '../src/components/stage-cpp/stage-cpp-clang.jsx');
|
||||
const uiSource = fs.readFileSync(uiPath, 'utf8');
|
||||
const ast = babel.parseSync(uiSource, {configFile: false, babelrc: false, parserOpts: {plugins: ['jsx']}});
|
||||
let callback;
|
||||
function visit(node) {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
if (node.type === 'ObjectProperty' && node.key.name === 'cmdPrint') callback = node.value;
|
||||
for (const value of Object.values(node)) {
|
||||
if (Array.isArray(value)) value.forEach(visit);
|
||||
else if (value && typeof value === 'object') visit(value);
|
||||
}
|
||||
}
|
||||
visit(ast);
|
||||
assert(callback, 'cmdPrint callback exists');
|
||||
for (const engineState of ['absent', 'ready', 'throwing']) {
|
||||
let displayed = '';
|
||||
const sent = [];
|
||||
const window = {};
|
||||
if (engineState !== 'absent') window.unityInstance = {SendMessage: (...args) => {
|
||||
if (engineState === 'throwing') throw new Error('engine unavailable');
|
||||
sent.push(args);
|
||||
}};
|
||||
const cmdPrint = vm.runInNewContext(`(${uiSource.slice(callback.start, callback.end)})`, {
|
||||
window, console: silentConsole, setCompilationOutput: update => {displayed = update(displayed);}
|
||||
});
|
||||
cmdPrint('A');
|
||||
cmdPrint('B\n');
|
||||
assert.strictEqual(displayed, 'AB\n');
|
||||
assert.strictEqual(window.cmd_print_text, 'AB\n');
|
||||
if (engineState === 'ready') assert.deepStrictEqual(sent[1], ['UIMain', 'SetText', 'AB\n']);
|
||||
console.log(`PASS output callback with ${engineState} engine`);
|
||||
|
||||
// Connect the real interpreter to the real UI callback, including repeated runs.
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
displayed = '';
|
||||
window.cmd_print_text = '';
|
||||
await run(customerEmailWithOutput, {moves: [0], turns: [-1]}, (executor, runtimeWindow) => {
|
||||
runtimeWindow.cppGameAPI.cmdPrint = cmdPrint;
|
||||
});
|
||||
assert.strictEqual(displayed, 'i=0\n');
|
||||
assert.strictEqual(window.cmd_print_text, 'i=0\n');
|
||||
if (engineState === 'ready') assert.deepStrictEqual(sent[sent.length - 1], ['UIMain', 'SetText', 'i=0\n']);
|
||||
}
|
||||
console.log(`PASS customer program through output callback with ${engineState} engine`);
|
||||
}
|
||||
console.log(`${cases.length + 9} regression checks passed`);
|
||||
console.log(`${cases.length + 4} regression checks passed`);
|
||||
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||
|
||||
Reference in New Issue
Block a user