Skip to content

Commit d2a8654

Browse files
feat: handle simple python tests (#398)
Simple meaning that it's not in the object form. i.e. not `{ test: () => { // code } }`, but simply `// code`
1 parent 522c8fb commit d2a8654

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

packages/python-evaluator/src/python-test-evaluator.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,54 @@ class PythonTestEvaluator implements TestEvaluator {
9797
const test: unknown = eval(testString);
9898
resolve(test);
9999
} catch (err) {
100-
if (
100+
const isUsingTopLevelAwait =
101101
err instanceof SyntaxError &&
102102
err.message.includes(
103103
"await is only valid in async functions and the top level bodies of modules",
104-
)
105-
) {
104+
);
105+
106+
if (isUsingTopLevelAwait) {
106107
const iifeTest = createAsyncIife(testString);
107108

108109
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
109110
eval(iifeTest).then(resolve).catch(reject);
110111
} else {
111-
reject(err as Error);
112+
// The assumption is that if we're in this block, then the test
113+
// is EITHER a standard JS test (in which case it should reject)
114+
// OR a Python test that uses the `input()` function (in which case
115+
// we need to fake the input function).
116+
117+
// While supporting both `{ test: () => { // test code } }` and
118+
// `// test code /` we have to fake input before running the
119+
// test. Otherwise any user code that uses `input()` will throw
120+
// an error.
121+
runPython(`
122+
def __fake_input(arg=None):
123+
return ""
124+
125+
input = __fake_input
126+
`);
127+
128+
// Evaluates the learner's code so that any variables they
129+
// define are available to the test.
130+
try {
131+
// It looks the source might be evaluated twice, but they're
132+
// on separate branches.
133+
runPython(opts.source ?? "");
134+
} catch (sourceErr) {
135+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
136+
reject(sourceErr);
137+
return;
138+
}
139+
140+
try {
141+
eval(testString);
142+
} catch (testErr) {
143+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
144+
reject(testErr);
145+
}
146+
147+
resolve(undefined);
112148
}
113149
}
114150
},

packages/tests/integration-tests/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,5 +1448,43 @@ pattern = re.compile('l+')`;
14481448
},
14491449
});
14501450
});
1451+
1452+
it("should support runPython in simple tests", async () => {
1453+
const result = await page.evaluate(async () => {
1454+
const runner = await window.FCCTestRunner.createTestRunner({
1455+
type: "python",
1456+
});
1457+
return runner?.runTest(`assert.equal(runPython('1 + 1'), 2)`);
1458+
});
1459+
expect(result).toEqual({ pass: true });
1460+
});
1461+
1462+
it("should evaluate user code before running tests", async () => {
1463+
const result = await page.evaluate(async () => {
1464+
const runner = await window.FCCTestRunner.createTestRunner({
1465+
type: "python",
1466+
source: "x = 3",
1467+
});
1468+
return runner?.runTest(`assert.equal(runPython('x + 1'), 5)`);
1469+
});
1470+
1471+
expect(result).toMatchObject({
1472+
err: {
1473+
actual: 4,
1474+
expected: 5,
1475+
},
1476+
});
1477+
});
1478+
1479+
it("should use a fake input function in simple tests", async () => {
1480+
const result = await page.evaluate(async () => {
1481+
const runner = await window.FCCTestRunner.createTestRunner({
1482+
type: "python",
1483+
});
1484+
return runner?.runTest(`assert.equal(runPython('input("test")'), "")`);
1485+
});
1486+
1487+
expect(result).toEqual({ pass: true });
1488+
});
14511489
});
14521490
});

0 commit comments

Comments
 (0)