Skip to content
Open
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
91 changes: 87 additions & 4 deletions .github/workflows/test-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Ensure submodules are initialized
Expand Down Expand Up @@ -94,9 +98,32 @@ jobs:
path: edge-linux-amd64.zip
if-no-files-found: error

- name: Test edge
# - name: Test edge
# run: |
# make test-only TEST_JOBS=4

- name: Install top-n test dependencies
shell: bash
run: |
set -euo pipefail
cd tests/top-n-test
npm ci --legacy-peer-deps

- name: Run top-n tests (node)
shell: bash
run: |
make test-only TEST_JOBS=4
set -euo pipefail
cd tests/top-n-test
node runner.js -r node --all

- name: Run top-n tests (edgejs)
shell: bash
env:
EDGEJS_BIN: ${{ github.workspace }}/build-edge/edge
run: |
set -euo pipefail
cd tests/top-n-test
node runner.js -r edgejs --all

- name: sccache stats
if: always()
Expand Down Expand Up @@ -218,6 +245,10 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Ensure submodules are initialized
Expand Down Expand Up @@ -255,9 +286,32 @@ jobs:
path: edge-darwin-arm64.zip
if-no-files-found: error

- name: Test edge
# - name: Test edge
# run: |
# make test-only TEST_JOBS=4

- name: Install top-n test dependencies
shell: bash
run: |
set -euo pipefail
cd tests/top-n-test
npm ci --legacy-peer-deps

- name: Run top-n tests (node)
shell: bash
run: |
set -euo pipefail
cd tests/top-n-test
node runner.js -r node --all

- name: Run top-n tests (edgejs)
shell: bash
env:
EDGEJS_BIN: ${{ github.workspace }}/build-edge/edge
run: |
make test-only TEST_JOBS=4
set -euo pipefail
cd tests/top-n-test
node runner.js -r edgejs --all

- name: sccache stats
if: always()
Expand Down Expand Up @@ -324,6 +378,35 @@ jobs:
set -euo pipefail
./wasix/build-wasix.sh

- name: Install Wasmer CLI
shell: bash
run: |
set -euo pipefail
curl -sSfL https://get.wasmer.io | sh -s "v7.1.0-rc.2"
echo "${HOME}/.wasmer/bin" >> "${GITHUB_PATH}"
"${HOME}/.wasmer/bin/wasmer" --version

- name: Install top-n test dependencies
shell: bash
run: |
set -euo pipefail
cd tests/top-n-test
npm ci --legacy-peer-deps

- name: Run top-n tests (wasix_edgejs)
shell: bash
run: |
set -euo pipefail
cd "${GITHUB_WORKSPACE}"
wasmer package build .
webc_path="$(find "${GITHUB_WORKSPACE}" -maxdepth 2 -type f -name '*.webc' | head -n 1)"
if [[ -z "${webc_path}" ]]; then
echo "Failed to locate generated .webc package after 'wasmer package build .'." >&2
exit 1
fi
cd tests/top-n-test
WASMER_BIN=wasmer EDGEJS_PACKAGE="${webc_path}" node runner.js -r wasix_edgejs --all --skip which, cross-spawn, playwright-core

- name: Generate dist
shell: bash
run: |
Expand Down
4 changes: 4 additions & 0 deletions tests/top-n-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.npm-cache/
results/
repos/
57 changes: 57 additions & 0 deletions tests/top-n-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# top-n-test

Smoke tests for the top-100 most popular npm packages. Each package has a hand-written test that exercises the library the way a real developer would use it. The suite can run against multiple runtimes (Node.js, EdgeJS, WASIX EdgeJS) to verify compatibility.

## Updating the package list

The top-100 list is based on npm `last-year` downloads and is locked by default. To refresh it:

```bash
node scripts/fetch-top-packages.js --count=100 --candidate-limit=500 --allow-update
```

Without `--allow-update`, the script refuses to overwrite the locked `top-packages.json`.

## Running tests

Three runners are available via `-r` / `--runner`:

| Runner | Command | Env vars |
|---|---|---|
| Node.js | `node runner.js -r node --all` | — |
| EdgeJS | `node runner.js -r edgejs --all` | `EDGEJS_BIN` (default: `edgejs`) |
| WASIX EdgeJS | `node runner.js -r wasix_edgejs --all` | see below |

The `wasix_edgejs` runner uses `wasmer-dev` to run tests inside the EdgeJS WASIX runtime. Configurable env vars:

| Env var | Default | Description |
|---|---|---|
| `WASMER_BIN` | `wasmer-dev` | Path to the Wasmer binary |
| `EDGEJS_PACKAGE` | `wasmer/edgejs` | Wasmer package to run |
| `WASMER_REGISTRY` | `wasmer.io` | Wasmer registry URL |

### Run a single package

```bash
node runner.js -r node chalk
```

### Skip specific packages

```bash
node runner.js -r node --all --skip ws,express
```

### Continue past failures

By default the runner stops on the first failure. To run everything:

```bash
node runner.js -r node --all --no-fail-fast
```

### List available packages

```bash
node runner.js --list
```
77 changes: 77 additions & 0 deletions tests/top-n-test/acorn-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';

const assert = require('node:assert/strict');

(async () => {
const acorn = require('acorn');

// Parse a simple variable declaration
const ast1 = acorn.parse('const x = 42;', { ecmaVersion: 'latest', sourceType: 'module' });
assert.equal(ast1.type, 'Program');
assert.ok(Array.isArray(ast1.body));
assert.equal(ast1.body.length, 1);
assert.equal(ast1.body[0].type, 'VariableDeclaration');
assert.equal(ast1.body[0].kind, 'const');
assert.equal(ast1.body[0].declarations[0].id.name, 'x');
assert.equal(ast1.body[0].declarations[0].init.value, 42);

// Parse a function declaration
const ast2 = acorn.parse('function greet(name) { return "hello " + name; }', { ecmaVersion: 'latest' });
assert.equal(ast2.body[0].type, 'FunctionDeclaration');
assert.equal(ast2.body[0].id.name, 'greet');
assert.equal(ast2.body[0].params.length, 1);
assert.equal(ast2.body[0].params[0].name, 'name');

// Parse an arrow function expression
const ast3 = acorn.parse('const add = (a, b) => a + b;', { ecmaVersion: 'latest' });
const decl = ast3.body[0].declarations[0];
assert.equal(decl.init.type, 'ArrowFunctionExpression');
assert.equal(decl.init.params.length, 2);
assert.equal(decl.init.params[0].name, 'a');
assert.equal(decl.init.params[1].name, 'b');
assert.equal(decl.init.body.type, 'BinaryExpression');
assert.equal(decl.init.body.operator, '+');

// Parse class declaration
const ast4 = acorn.parse('class Foo extends Bar { constructor() { super(); } }', { ecmaVersion: 'latest' });
assert.equal(ast4.body[0].type, 'ClassDeclaration');
assert.equal(ast4.body[0].id.name, 'Foo');
assert.equal(ast4.body[0].superClass.name, 'Bar');

// Parse template literal
const ast5 = acorn.parse('const s = `hello ${name}`;', { ecmaVersion: 'latest' });
const tmpl = ast5.body[0].declarations[0].init;
assert.equal(tmpl.type, 'TemplateLiteral');
assert.ok(Array.isArray(tmpl.quasis));
assert.ok(Array.isArray(tmpl.expressions));
assert.equal(tmpl.expressions[0].name, 'name');

// Invalid syntax should throw
assert.throws(() => {
acorn.parse('const = ;', { ecmaVersion: 'latest' });
}, /Unexpected token/);

// parseExpressionAt - parse an expression at a given offset
if (typeof acorn.parseExpressionAt === 'function') {
const expr = acorn.parseExpressionAt('void 1 + 2', 5, { ecmaVersion: 'latest' });
assert.equal(expr.type, 'BinaryExpression');
}

// tokenizer
if (typeof acorn.tokenizer === 'function') {
const tokens = [...acorn.tokenizer('let x = 1;', { ecmaVersion: 'latest' })];
assert.ok(tokens.length > 0);
// Each token has a type and value
for (const token of tokens) {
assert.ok('type' in token);
assert.ok('start' in token);
assert.ok('end' in token);
}
}

console.log('acorn-test:ok');
})().catch((err) => {
console.error('acorn-test:fail');
console.error(err && err.stack ? err.stack : String(err));
process.exit(1);
});
88 changes: 88 additions & 0 deletions tests/top-n-test/ajv-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict';

const assert = require('node:assert/strict');

(async () => {
const Ajv = require('ajv');

// Create an Ajv instance and compile a basic schema
const ajv = new Ajv();
const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
age: { type: 'integer', minimum: 0 },
email: { type: 'string' },
},
required: ['name', 'age'],
additionalProperties: false,
};

const validate = ajv.compile(schema);

// Valid data passes
assert.equal(validate({ name: 'Alice', age: 30 }), true);
assert.equal(validate({ name: 'Bob', age: 25, email: 'bob@test.com' }), true);
assert.equal(validate.errors, null);

// Missing required field fails
assert.equal(validate({ name: 'Alice' }), false);
assert.ok(Array.isArray(validate.errors));
assert.ok(validate.errors.length > 0);
assert.equal(validate.errors[0].keyword, 'required');

// Wrong type fails
assert.equal(validate({ name: 'Alice', age: 'thirty' }), false);
assert.ok(validate.errors.some(e => e.keyword === 'type'));

// Additional properties are rejected
assert.equal(validate({ name: 'Alice', age: 30, role: 'admin' }), false);
assert.ok(validate.errors.some(e => e.keyword === 'additionalProperties'));

// minLength constraint
assert.equal(validate({ name: '', age: 30 }), false);
assert.ok(validate.errors.some(e => e.keyword === 'minLength'));

// minimum constraint
assert.equal(validate({ name: 'Alice', age: -1 }), false);
assert.ok(validate.errors.some(e => e.keyword === 'minimum'));

// Compile a schema with nested objects and arrays
const complexSchema = {
type: 'object',
properties: {
tags: {
type: 'array',
items: { type: 'string' },
minItems: 1,
},
address: {
type: 'object',
properties: {
street: { type: 'string' },
zip: { type: 'string', pattern: '^[0-9]{5}$' },
},
required: ['street'],
},
},
};

const validateComplex = ajv.compile(complexSchema);
assert.equal(validateComplex({ tags: ['a', 'b'], address: { street: '123 Main' } }), true);
assert.equal(validateComplex({ tags: [] }), false); // minItems
assert.equal(validateComplex({ tags: [123] }), false); // items type
assert.equal(validateComplex({ address: { street: '123 Main', zip: 'abc' } }), false); // pattern

// Use addSchema / getSchema pattern
ajv.addSchema({ type: 'string', minLength: 2 }, 'shortString');
const shortStringValidate = ajv.getSchema('shortString');
assert.equal(typeof shortStringValidate, 'function');
assert.equal(shortStringValidate('hi'), true);
assert.equal(shortStringValidate('x'), false);

console.log('ajv-test:ok');
})().catch((err) => {
console.error('ajv-test:fail');
console.error(err && err.stack ? err.stack : String(err));
process.exit(1);
});
Loading
Loading