silphius/app/astride/sandbox.test.js
2024-06-22 10:47:17 -05:00

260 lines
5.1 KiB
JavaScript

import {parse as acornParse} from 'acorn';
import {expect, test} from 'vitest';
import Sandbox from '@/astride/sandbox.js';
function parse(code, options = {}) {
return acornParse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
...options,
})
}
test('declares variables', async () => {
const sandbox = new Sandbox(
await parse(`
const scalar = true ? +1 : 32;
const asyncScalar = await 2;
const array = [3, 4, 5];
const asyncArray = await [6, 7, 8];
const object = {9: '10'};
const asyncObject = await {11: '12'};
`),
);
let result;
do {
result = sandbox.step();
if (result.value?.async) {
await result.value.async;
}
} while (!result.done);
expect(sandbox.context)
.to.deep.equal({
scalar: 1,
asyncScalar: 2,
array: [3, 4, 5],
asyncArray: [6, 7, 8],
object: {9: '10'},
asyncObject: {11: '12'},
});
});
test('scopes variables', async () => {
const sandbox = new Sandbox(
await parse(`
const result = [];
const scalar = 1;
result.push(scalar);
{
const scalar = 2;
result.push(scalar);
}
result.push(scalar);
`),
);
let result;
do {
result = sandbox.step();
if (result.value?.async) {
await result.value.async;
}
} while (!result.done);
expect(sandbox.context.result)
.to.deep.equal([1, 2, 1]);
});
test('destructures variables', async () => {
const sandbox = new Sandbox(
await parse(`
const [a, , c] = [1, 2, 3];
const {x: x1, y, z: {zz}} = {x: 4, y: 5, z: {zz: 6}};
const [d, e] = await [7, 8];
const {t, u: {uu}} = {t: 9, u: {uu: await 10}};
`),
);
let result;
do {
result = sandbox.step();
if (result.value?.async) {
await result.value.value;
}
} while (!result.done);
expect(sandbox.context)
.to.deep.equal({
a: 1,
c: 3,
x1: 4,
y: 5,
zz: 6,
d: 7,
e: 8,
t: 9,
uu: 10,
});
});
test('runs arbitrary number of ops', async () => {
const sandbox = new Sandbox(
await parse(`
const foo = [];
for (let i = 0; i < 1500; ++i) {
foo.push(i);
}
`),
);
sandbox.run(1000);
expect(sandbox.context.foo.length)
.to.equal(1000);
sandbox.run(1000);
expect(sandbox.context.foo.length)
.to.equal(1500);
expect(true)
.to.be.true;
});
test('evaluates conditional branches', async () => {
const sandbox = new Sandbox(
await parse(`
let foo, bar;
if (true) {
foo = 1;
}
else {
foo = 2;
}
if (await false) {
bar = 1;
}
else {
bar = 2;
}
`),
);
let result;
do {
result = sandbox.step();
if (result.value?.async) {
await result.value.value;
}
} while (!result.done);
expect(sandbox.context)
.to.deep.equal({
foo: 1,
bar: 2,
});
});
test('evaluates loops', async () => {
const sandbox = new Sandbox(
await parse(`
let x = 0, y = 0, a = 0, b = 0, c = 0;
for (let i = 0; i < 3; ++i) {
x += 1;
}
for (let i = await 0; i < await 3; i = 1 + await i) {
y += 1;
}
do {
a += 1;
} while (a < 3);
do {
b += 1;
} while (await b < 3);
while (c < 3) {
c += 1;
}
`),
);
let result;
do {
result = sandbox.step();
if (result.value?.async) {
await result.value.value;
}
} while (!result.done);
expect(sandbox.context)
.to.deep.equal({
a: 3,
b: 3,
c: 3,
x: 3,
y: 3,
});
});
test('evaluates undefined for nonexistent variables in scope', async () => {
const sandbox = new Sandbox(
await parse(`
const x = y
`),
);
sandbox.run();
expect(sandbox.context)
.to.deep.equal({
x: undefined,
});
});
test('returns values at the top level', async () => {
let sandbox;
sandbox = new Sandbox(
await parse(`
x = 16
y = 4
return [x * 3, y * 3]
x = 32
y = 8
`, {allowReturnOutsideFunction: true}),
);
const {value: {value}} = sandbox.run();
expect(value)
.to.deep.equal([48, 12]);
expect(sandbox.context)
.to.deep.equal({x: 16, y: 4});
sandbox = new Sandbox(
await parse(`
x = 16
y = 4
return
x = 32
y = 8
`, {allowReturnOutsideFunction: true}),
);
sandbox.run();
expect(sandbox.context)
.to.deep.equal({x: 16, y: 4});
});
test('sets variables in global scope', async () => {
const sandbox = new Sandbox(
await parse(`
x = y
`),
);
sandbox.run();
expect(sandbox.context)
.to.deep.equal({
x: undefined,
});
});
test('runs arbitrary number of ops', async () => {
const sandbox = new Sandbox(
await parse(`
const foo = [];
for (let i = 0; i < 1500; ++i) {
foo.push(i);
}
`),
);
sandbox.run(1000);
expect(sandbox.context.foo.length)
.to.equal(1000);
sandbox.run(1000);
expect(sandbox.context.foo.length)
.to.equal(1500);
expect(true)
.to.be.true;
});