Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 17 additions & 22 deletions .github/workflows/web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,32 @@ concurrency:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- name: Checkout
uses: actions/checkout@v4

- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.99.0
target: wasm32-unknown-unknown

- uses: taiki-e/install-action@v2
- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
tool: wasm-pack
version: 6.0.12

- name: Install Emscripten
run: |
sudo apt-get update
sudo apt-get install --yes emscripten

- name: Build WASM
run: |
wasm-pack build crates/val-wasm --target web \
--out-name val \
--out-dir ../../www/packages/val-wasm \
--locked

- name: Check generated types
run: git diff --exit-code -- www/packages/val-wasm/val.d.ts
- name: Setup build tools
uses: taiki-e/install-action@v2
with:
tool: just@1.58.0,wasm-pack@0.15.0

- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
bun-version: 1.4.3

- name: Install dependencies
working-directory: ./www
Expand All @@ -58,9 +51,11 @@ jobs:
working-directory: ./www
run: bun run lint

- name: Test
working-directory: ./www
run: bun test --pass-with-no-tests
- name: Build and test WASM
run: just test-wasm

- name: Check generated types
run: git diff --exit-code -- www/packages/val-wasm/val.d.ts

- name: Build
working-directory: ./www
Expand Down
8 changes: 6 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ build:

[group: 'web']
build-wasm:
wasm-pack build crates/val-wasm --target web \
wasm-pack build crates/val-wasm --target web --release \
--out-name val \
--out-dir ../../www/packages/val-wasm
--out-dir ../../www/packages/val-wasm --locked

[group: 'check']
check:
Expand Down Expand Up @@ -82,6 +82,10 @@ serve-web: build-wasm
test:
cargo test

[group: 'test']
test-wasm: build-wasm
cd www && bun test

[group: 'test']
test-release-workflow:
-git tag -d test-release
Expand Down
165 changes: 165 additions & 0 deletions www/tests/wasm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { expect, test } from 'bun:test';
import { readFile } from 'node:fs/promises';
import init, { execute, parse } from 'val-wasm';

const wasm = await init({
module_or_path: await readFile(
new URL('../packages/val-wasm/val_bg.wasm', import.meta.url)
),
});

test.each([
['(2 ^ 128 + 1) - 2 ^ 128', '1'],
['(1 / 3) * 3', '1'],
['sqrt(4)', '2'],
])('executes arbitrary precision arithmetic: %s', (source, value) => {
expect(execute(source, '')).toEqual({
errors: [],
exitCode: 0,
output: '',
value,
});
});

test('executes input and output and resets the runtime', () => {
expect(
execute(
'foo = input("foo"); print(foo); println(input()); input()',
'bar\r\nbaz\nqux'
)
).toEqual({
errors: [],
exitCode: 0,
output: 'foobarbaz\n',
value: 'qux',
});

expect(execute('input()', '')).toEqual({
errors: [],
exitCode: 0,
output: '',
value: '',
});
});

test.each([
[
'"foo',
[
{
kind: 'Parser',
message: "found end of input expected something else, or '\"'",
range: { start: 4, end: 4 },
},
],
],
[
'"😀"; foo',
[
{
kind: 'Evaluator',
message: 'Undefined variable `foo`',
range: { start: 6, end: 9 },
},
],
],
])('returns errors with UTF-16 ranges: %s', (source, errors) => {
expect(execute(source, '')).toEqual({
errors,
exitCode: 1,
output: '',
});
});

test('stops execution at exit and preserves output', () => {
expect(execute('println("foo"); exit(7); println("bar")', '')).toEqual({
errors: [],
exitCode: 7,
output: 'foo\n',
});
});

test('parses through the generated bindings', () => {
expect(parse('"😀"')).toEqual({
status: 'ok',
ast: {
kind: 'statements',
range: { start: 0, end: 4 },
children: [
{
kind: 'expression',
range: { start: 0, end: 4 },
children: [
{
kind: 'string',
range: { start: 0, end: 4 },
children: [],
},
],
},
],
},
});
});

test.each([0, 1, 16, 17])('allocates and frees %i aligned bytes', (size) => {
wasm.free(0);

const pointer = wasm.malloc(size);

expect(pointer).not.toBe(0);
expect(pointer % 16).toBe(0);

new Uint8Array(wasm.memory.buffer, pointer, size).fill(42);
wasm.free(pointer);
});

test('reallocates null, grows and shrinks while preserving bytes', () => {
const bytes = new TextEncoder().encode('foobar');
const pointer = wasm.realloc(0, bytes.length);

expect(pointer).not.toBe(0);
expect(pointer % 16).toBe(0);
new Uint8Array(wasm.memory.buffer, pointer, bytes.length).set(bytes);

const grown = wasm.realloc(pointer, 128 * 1024);

expect(grown).not.toBe(0);
expect(grown % 16).toBe(0);
expect(new Uint8Array(wasm.memory.buffer, grown, bytes.length)).toEqual(
bytes
);

const shrunk = wasm.realloc(grown, 3);

expect(shrunk).not.toBe(0);
expect(shrunk % 16).toBe(0);
expect(new Uint8Array(wasm.memory.buffer, shrunk, 3)).toEqual(
bytes.subarray(0, 3)
);

const empty = wasm.realloc(shrunk, 0);

expect(empty).not.toBe(0);
expect(empty % 16).toBe(0);
wasm.free(empty);
});

test.each([0x7fffffe1, 0x7fffffff, 0xfffffff0, 0xffffffff])(
'rejects allocation size %i without changing existing memory',
(size) => {
const bytes = new TextEncoder().encode('foo');
const pointer = wasm.malloc(bytes.length);

expect(pointer).not.toBe(0);
new Uint8Array(wasm.memory.buffer, pointer, bytes.length).set(bytes);

expect(wasm.malloc(size)).toBe(0);
expect(wasm.realloc(0, size)).toBe(0);
expect(wasm.realloc(pointer, size)).toBe(0);
expect(new Uint8Array(wasm.memory.buffer, pointer, bytes.length)).toEqual(
bytes
);
wasm.free(pointer);
}
);
Loading