Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 5 additions & 18 deletions src/componentize.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@bytecodealliance/jco';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { resolve, join, dirname } from 'node:path';
import { join, dirname } from 'node:path';
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
import { rmSync, existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
Expand All @@ -17,26 +17,13 @@ import {
stubWasi,
} from '../lib/spidermonkey-embedding-splicer.js';
import { fileURLToPath } from 'node:url';
import { cwd, stdout, platform } from 'node:process';
import { cwd, stdout } from 'node:process';

import { maybeWindowsPath } from './platform.js';

export const { version } = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8'),
);
const isWindows = platform === 'win32';

function maybeWindowsPath(path) {
if (!path) return path;
const resolvedPath = resolve(path);
if (!isWindows) return resolvedPath;

// Strip any existing UNC prefix check both the format we add as well as what
// the windows API returns when using path.resolve
let cleanPath = resolvedPath;
while (cleanPath.startsWith('\\\\?\\') || cleanPath.startsWith('//?/')) {
cleanPath = cleanPath.substring(4);
}

return '//?/' + cleanPath.replace(/\\/g, '/');
}

/**
* Clean up the given input string by removing the given patterns if
Expand Down
19 changes: 19 additions & 0 deletions src/platform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { resolve } from 'node:path';
import { platform } from 'node:process';

export const isWindows = platform === 'win32';

export function maybeWindowsPath(path) {
if (!path) return path;
const resolvedPath = resolve(path);
if (!isWindows) return resolvedPath;

// Strip any existing UNC prefix check both the format we add as well as what
// the windows API returns when using path.resolve
let cleanPath = resolvedPath;
while (cleanPath.startsWith('\\\\?\\') || cleanPath.startsWith('//?/')) {
cleanPath = cleanPath.substring(4);
}

return '//?/' + cleanPath.replace(/\\/g, '/');
}
58 changes: 54 additions & 4 deletions test/builtins/fetch.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
import { URL, fileURLToPath } from 'node:url';
import { createServer } from 'node:http';

import { strictEqual, ok } from 'node:assert';

export const source = `
import { maybeWindowsPath } from '../../src/platform.js';
import { getRandomPort } from '../util.js';

const FETCH_URL = 'http://localhost';

export const state = async () => {
const port = await getRandomPort();
return { port };
};

export const source = (testState) => {
let port = testState?.port ? ':' + testState.port : '';
Comment thread
vados-cosmonic marked this conversation as resolved.
Outdated
const url = FETCH_URL + port;
return `
export async function run () {
const res = await fetch('https://httpbin.org/anything');
const res = await fetch('${url}');
const source = await res.json();
console.log(source.url);
}
export function ready () {
return true;
}
`;
};

export const enableFeatures = ['http'];

export async function test(run) {
export async function test(run, testState) {
// Get the randomly generated port
const port = testState.port;
if (!port) {
throw new Error('missing port on test state');
}

const url = FETCH_URL + (port ? ':' + port : '');

// Run a local server on some port
const server = createServer(async (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.write(
JSON.stringify({
status: 'ok',
url,
}),
);
res.end();
}).listen(port);

// Wait until the server is ready
let ready = false;
while (!ready) {
try {
const res = await fetch(url);
ready = true;
} catch (err) {
await new Promise((resolve) => setTimeout(resolve, 250));
}
}

const { stdout, stderr } = await run();
strictEqual(stderr, '');
strictEqual(stdout.trim(), 'https://httpbin.org/anything');
strictEqual(stdout.trim(), url);

server.close();
}
84 changes: 50 additions & 34 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const LOG_DEBUGGING = false;
const enableAot = process.env.WEVAL_TEST == '1';
const debugBuild = process.env.DEBUG_TEST == '1';

const noOp = async () => {};

function maybeLogging(disableFeatures) {
if (!LOG_DEBUGGING) return disableFeatures;
if (disableFeatures && disableFeatures.includes('stdio')) {
Expand All @@ -24,12 +26,24 @@ suite('Builtins', () => {
for (const filename of builtinsCases) {
const name = filename.slice(0, -3);
test(name, async () => {
const testModule = await import(`./builtins/${filename}`);
const {
source,
state,
test: runTest,
disableFeatures,
enableFeatures,
} = await import(`./builtins/${filename}`);
} = testModule;

// If an args object was provided, generate the arguments to feed to both
// source generation (if necessary) and the test run itself
let stateFn = state ?? noOp;
const stateObj = await stateFn();

// If the source is a function then invoke it to generate the source string, possibly with arguments
let source = testModule.source;
if (typeof source === 'function') {
source = source(stateObj);
}

const { component } = await componentize(
source,
Expand All @@ -46,7 +60,7 @@ suite('Builtins', () => {
enableFeatures,
disableFeatures: maybeLogging(disableFeatures),
enableAot,
}
},
);

const { files } = await transpile(component, {
Expand All @@ -61,13 +75,13 @@ suite('Builtins', () => {

await writeFile(
new URL(`./output/${name}.component.wasm`, import.meta.url),
component
component,
);

for (const file of Object.keys(files)) {
await writeFile(
new URL(`./output/${name}/${file}`, import.meta.url),
files[file]
files[file],
);
}

Expand All @@ -76,11 +90,12 @@ suite('Builtins', () => {
`
import { run } from './${name}.js';
run();
`
`,
);

try {
await runTest(async function run() {
// Build a run function to pass to the test
const runFn = async function run() {
let stdout = '',
stderr = '',
timeout;
Expand All @@ -90,10 +105,10 @@ suite('Builtins', () => {
process.argv[0],
[
fileURLToPath(
new URL(`./output/${name}/run.js`, import.meta.url)
new URL(`./output/${name}/run.js`, import.meta.url),
),
],
{ stdio: 'pipe' }
{ stdio: 'pipe' },
);
cp.stdout.on('data', (chunk) => {
stdout += chunk;
Expand All @@ -103,16 +118,16 @@ suite('Builtins', () => {
});
cp.on('error', reject);
cp.on('exit', (code) =>
code === 0 ? resolve() : reject(new Error(stderr || stdout))
code === 0 ? resolve() : reject(new Error(stderr || stdout)),
);
timeout = setTimeout(() => {
reject(
new Error(
'test timed out with output:\n' +
stdout +
'\n\nstderr:\n' +
stderr
)
stdout +
'\n\nstderr:\n' +
stderr,
),
);
}, 10_000);
});
Expand All @@ -123,7 +138,10 @@ suite('Builtins', () => {
}

return { stdout, stderr };
});
};

// Run the actual test
await runTest(runFn, stateObj);
} catch (err) {
if (err.stderr) console.error(err.stderr);
throw err.err || err;
Expand All @@ -138,7 +156,7 @@ suite('Bindings', () => {
test(name, async () => {
const source = await readFile(
new URL(`./cases/${name}/source.js`, import.meta.url),
'utf8'
'utf8',
);

let witWorld,
Expand All @@ -148,14 +166,14 @@ suite('Bindings', () => {
try {
witWorld = await readFile(
new URL(`./cases/${name}/world.wit`, import.meta.url),
'utf8'
'utf8',
);
} catch (e) {
if (e?.code == 'ENOENT') {
try {
isWasiTarget = true;
witPath = fileURLToPath(
new URL(`./cases/${name}/wit`, import.meta.url)
new URL(`./cases/${name}/wit`, import.meta.url),
);
await readdir(witPath);
} catch (e) {
Expand Down Expand Up @@ -229,14 +247,14 @@ suite('Bindings', () => {

await writeFile(
new URL(`./output/${name}.component.wasm`, import.meta.url),
component
component,
);

for (const file of Object.keys(files)) {
let source = files[file];
await writeFile(
new URL(`./output/${name}/${file}`, import.meta.url),
source
source,
);
}

Expand Down Expand Up @@ -274,12 +292,12 @@ suite('WASI', () => {
worldName: 'test1',
enableAot,
debugBuild,
}
},
);

await writeFile(
new URL(`./output/wasi.component.wasm`, import.meta.url),
component
component,
);

const { files } = await transpile(component, { tracing: DEBUG_TRACING });
Expand All @@ -291,7 +309,7 @@ suite('WASI', () => {
for (const file of Object.keys(files)) {
await writeFile(
new URL(`./output/wasi/${file}`, import.meta.url),
files[file]
files[file],
);
}

Expand All @@ -303,19 +321,17 @@ suite('WASI', () => {
});

test('basic app (OriginalSourceFile API)', async () => {
const { component } = await componentize(
{
sourcePath: "./test/api/index.js",
witPath: fileURLToPath(new URL('./wit', import.meta.url)),
worldName: 'test1',
enableAot,
debugBuild,
}
);
const { component } = await componentize({
sourcePath: './test/api/index.js',
witPath: fileURLToPath(new URL('./wit', import.meta.url)),
worldName: 'test1',
enableAot,
debugBuild,
});

await writeFile(
new URL(`./output/wasi.component.wasm`, import.meta.url),
component
component,
);

const { files } = await transpile(component, { tracing: DEBUG_TRACING });
Expand All @@ -327,7 +343,7 @@ suite('WASI', () => {
for (const file of Object.keys(files)) {
await writeFile(
new URL(`./output/wasi/${file}`, import.meta.url),
files[file]
files[file],
);
}

Expand Down
13 changes: 13 additions & 0 deletions test/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createServer } from 'node:net';

// Utility function for getting a random port
export async function getRandomPort() {
return await new Promise((resolve) => {
const server = createServer();
server.listen(0, function () {
const port = this.address().port;
server.on('close', () => resolve(port));
server.close();
});
});
}