mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
esm: misc test refactors
- add test specific to the event loop - move parallel tests into es-module folder - refactor fixture to add braces for if blocks - use 'os' instead of 'fs' as placeholder - spelling PR-URL: https://github.com/nodejs/node/pull/46631 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
parent
136456a23a
commit
9e840deecc
9 changed files with 381 additions and 62 deletions
|
@ -3,8 +3,7 @@ import { mustCall } from '../common/index.mjs';
|
|||
import assert from 'assert';
|
||||
|
||||
const dirname = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
|
||||
const fixtures = dirname.slice(0, dirname.lastIndexOf('/', dirname.length - 2) +
|
||||
1) + 'fixtures/';
|
||||
const fixtures = dirname.slice(0, dirname.lastIndexOf('/', dirname.length - 2) + 1) + 'fixtures/';
|
||||
|
||||
(async () => {
|
||||
assert.strictEqual(await import.meta.resolve('./test-esm-import-meta.mjs'),
|
||||
|
|
|
@ -5,7 +5,7 @@ import { execPath } from 'node:process';
|
|||
import { describe, it } from 'node:test';
|
||||
|
||||
|
||||
describe('ESM: ensure initialisation happens only once', { concurrency: true }, () => {
|
||||
describe('ESM: ensure initialization happens only once', { concurrency: true }, () => {
|
||||
it(async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--loader',
|
||||
|
|
|
@ -9,7 +9,7 @@ const setupArgs = [
|
|||
'--input-type=module',
|
||||
'--eval',
|
||||
];
|
||||
const commonInput = 'import fs from "node:fs"; console.log(fs)';
|
||||
const commonInput = 'import os from "node:os"; console.log(os)';
|
||||
const commonArgs = [
|
||||
...setupArgs,
|
||||
commonInput,
|
||||
|
@ -114,11 +114,11 @@ describe('ESM: loader chaining', { concurrency: true }, () => {
|
|||
);
|
||||
|
||||
assert.match(stdout, /^resolve arg count: 3$/m);
|
||||
assert.match(stdout, /specifier: 'node:fs'/);
|
||||
assert.match(stdout, /specifier: 'node:os'/);
|
||||
assert.match(stdout, /next: \[AsyncFunction: nextResolve\]/);
|
||||
|
||||
assert.match(stdout, /^load arg count: 3$/m);
|
||||
assert.match(stdout, /url: 'node:fs'/);
|
||||
assert.match(stdout, /url: 'node:os'/);
|
||||
assert.match(stdout, /next: \[AsyncFunction: nextLoad\]/);
|
||||
});
|
||||
|
||||
|
|
12
test/es-module/test-esm-loader-event-loop.mjs
Normal file
12
test/es-module/test-esm-loader-event-loop.mjs
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Flags: --experimental-loader ./test/fixtures/es-module-loaders/hooks-custom.mjs
|
||||
import { mustCall } from '../common/index.mjs';
|
||||
|
||||
const done = mustCall();
|
||||
|
||||
|
||||
// Test that the process doesn't exit because of a caught exception thrown as part of dynamic import().
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await import('nonexistent/file.mjs').catch(() => {});
|
||||
}
|
||||
|
||||
done();
|
288
test/es-module/test-esm-loader-spawn-promisified.mjs
Normal file
288
test/es-module/test-esm-loader-spawn-promisified.mjs
Normal file
|
@ -0,0 +1,288 @@
|
|||
import { spawnPromisified } from '../common/index.mjs';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import assert from 'node:assert';
|
||||
import { execPath } from 'node:process';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
describe('Loader hooks throwing errors', { concurrency: true }, () => {
|
||||
it('throws on nonexistent modules', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "nonexistent/file.mjs"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_MODULE_NOT_FOUND/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on unknown extensions', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import '${fixtures.fileURL('/es-modules/file.unknown')}'`,
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on invalid return values', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/badReturnVal.mjs"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on boolean false', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/format.false"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
it('throws on boolean true', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/format.true"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on invalid returned object', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/badReturnFormatVal.mjs"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on unsupported format', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/unsupportedReturnFormatVal.mjs"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_UNKNOWN_MODULE_FORMAT/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('throws on invalid format property type', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
'import "esmHook/badReturnSourceVal.mjs"',
|
||||
]);
|
||||
|
||||
assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/);
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 1);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('rejects dynamic imports for all of the error cases checked above', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await Promise.allSettled([
|
||||
import('nonexistent/file.mjs'),
|
||||
import('${fixtures.fileURL('/es-modules/file.unknown')}'),
|
||||
import('esmHook/badReturnVal.mjs'),
|
||||
import('esmHook/format.false'),
|
||||
import('esmHook/format.true'),
|
||||
import('esmHook/badReturnFormatVal.mjs'),
|
||||
import('esmHook/unsupportedReturnFormatVal.mjs'),
|
||||
import('esmHook/badReturnSourceVal.mjs'),
|
||||
]).then((results) => {
|
||||
assert.strictEqual(results.every((result) => result.status === 'rejected'), true);
|
||||
})`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Loader hooks parsing modules', { concurrency: true }, () => {
|
||||
it('can parse .js files as ESM', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await import('${fixtures.fileURL('/es-module-loaders/js-as-esm.js')}')
|
||||
.then((parsedModule) => {
|
||||
assert.strictEqual(typeof parsedModule, 'object');
|
||||
assert.strictEqual(parsedModule.namedExport, 'named-export');
|
||||
})`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('can define .ext files as ESM', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await import('${fixtures.fileURL('/es-modules/file.ext')}')
|
||||
.then((parsedModule) => {
|
||||
assert.strictEqual(typeof parsedModule, 'object');
|
||||
const { default: defaultExport } = parsedModule;
|
||||
assert.strictEqual(typeof defaultExport, 'function');
|
||||
assert.strictEqual(defaultExport.name, 'iAmReal');
|
||||
assert.strictEqual(defaultExport(), true);
|
||||
})`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('can predetermine the format in the custom loader resolve hook', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await import('esmHook/preknownFormat.pre')
|
||||
.then((parsedModule) => {
|
||||
assert.strictEqual(typeof parsedModule, 'object');
|
||||
assert.strictEqual(parsedModule.default, 'hello world');
|
||||
})`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('can provide source for a nonexistent file', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await import('esmHook/virtual.mjs')
|
||||
.then((parsedModule) => {
|
||||
assert.strictEqual(typeof parsedModule, 'object');
|
||||
assert.strictEqual(typeof parsedModule.default, 'undefined');
|
||||
assert.strictEqual(parsedModule.message, 'WOOHOO!');
|
||||
})`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('ensures that loaders have a separate context from userland', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import assert from 'node:assert';
|
||||
await import('${fixtures.fileURL('/es-modules/stateful.mjs')}')
|
||||
.then(({ default: count }) => {
|
||||
assert.strictEqual(count(), 1);
|
||||
});`,
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
|
||||
it('ensures that user loaders are not bound to the internal loader', async () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
fixtures.fileURL('/es-module-loaders/loader-this-value-inside-hook-functions.mjs'),
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
';', // Actual test is inside the loader module.
|
||||
]);
|
||||
|
||||
assert.strictEqual(stderr, '');
|
||||
assert.strictEqual(stdout, '');
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
});
|
||||
});
|
128
test/fixtures/es-module-loaders/hooks-custom.mjs
vendored
128
test/fixtures/es-module-loaders/hooks-custom.mjs
vendored
|
@ -6,6 +6,31 @@ import count from '../es-modules/stateful.mjs';
|
|||
// used to assert node-land and user-land have different contexts
|
||||
count();
|
||||
|
||||
export function resolve(specifier, { importAssertions }, next) {
|
||||
let format = '';
|
||||
|
||||
if (specifier === 'esmHook/format.false') {
|
||||
format = false;
|
||||
}
|
||||
if (specifier === 'esmHook/format.true') {
|
||||
format = true;
|
||||
}
|
||||
if (specifier === 'esmHook/preknownFormat.pre') {
|
||||
format = 'module';
|
||||
}
|
||||
|
||||
if (specifier.startsWith('esmHook')) {
|
||||
return {
|
||||
format,
|
||||
shortCircuit: true,
|
||||
url: pathToFileURL(specifier).href,
|
||||
importAssertions,
|
||||
};
|
||||
}
|
||||
|
||||
return next(specifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url A fully resolved file url.
|
||||
* @param {object} context Additional info.
|
||||
|
@ -15,63 +40,62 @@ count();
|
|||
*/
|
||||
export function load(url, context, next) {
|
||||
// Load all .js files as ESM, regardless of package scope
|
||||
if (url.endsWith('.js')) return next(url, {
|
||||
...context,
|
||||
format: 'module',
|
||||
});
|
||||
|
||||
if (url.endsWith('.ext')) return next(url, {
|
||||
...context,
|
||||
format: 'module',
|
||||
});
|
||||
|
||||
if (url.endsWith('esmHook/badReturnVal.mjs')) return 'export function returnShouldBeObject() {}';
|
||||
|
||||
if (url.endsWith('esmHook/badReturnFormatVal.mjs')) return {
|
||||
format: Array(0),
|
||||
shortCircuit: true,
|
||||
source: '',
|
||||
}
|
||||
if (url.endsWith('esmHook/unsupportedReturnFormatVal.mjs')) return {
|
||||
format: 'foo', // Not one of the allowable inputs: no translator named 'foo'
|
||||
shortCircuit: true,
|
||||
source: '',
|
||||
if (url.endsWith('.js')) {
|
||||
return next(url, {
|
||||
...context,
|
||||
format: 'module',
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/badReturnSourceVal.mjs')) return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: Array(0),
|
||||
if (url.endsWith('.ext')) {
|
||||
return next(url, {
|
||||
...context,
|
||||
format: 'module',
|
||||
});
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/preknownFormat.pre')) return {
|
||||
format: context.format,
|
||||
shortCircuit: true,
|
||||
source: `const msg = 'hello world'; export default msg;`
|
||||
};
|
||||
if (url.endsWith('esmHook/badReturnVal.mjs')) {
|
||||
return 'export function returnShouldBeObject() {}';
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/virtual.mjs')) return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: `export const message = 'Woohoo!'.toUpperCase();`,
|
||||
};
|
||||
if (url.endsWith('esmHook/badReturnFormatVal.mjs')) {
|
||||
return {
|
||||
format: Array(0),
|
||||
shortCircuit: true,
|
||||
source: '',
|
||||
};
|
||||
}
|
||||
if (url.endsWith('esmHook/unsupportedReturnFormatVal.mjs')) {
|
||||
return {
|
||||
format: 'foo', // Not one of the allowable inputs: no translator named 'foo'
|
||||
shortCircuit: true,
|
||||
source: '',
|
||||
};
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/badReturnSourceVal.mjs')) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: Array(0),
|
||||
};
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/preknownFormat.pre')) {
|
||||
return {
|
||||
format: context.format,
|
||||
shortCircuit: true,
|
||||
source: `const msg = 'hello world'; export default msg;`
|
||||
};
|
||||
}
|
||||
|
||||
if (url.endsWith('esmHook/virtual.mjs')) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: `export const message = 'Woohoo!'.toUpperCase();`,
|
||||
};
|
||||
}
|
||||
|
||||
return next(url);
|
||||
}
|
||||
|
||||
export function resolve(specifier, { importAssertions }, next) {
|
||||
let format = '';
|
||||
|
||||
if (specifier === 'esmHook/format.false') format = false;
|
||||
if (specifier === 'esmHook/format.true') format = true;
|
||||
if (specifier === 'esmHook/preknownFormat.pre') format = 'module';
|
||||
|
||||
if (specifier.startsWith('esmHook')) return {
|
||||
format,
|
||||
shortCircuit: true,
|
||||
url: pathToFileURL(specifier).href,
|
||||
importAssertions,
|
||||
};
|
||||
|
||||
return next(specifier);
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs
|
||||
import '../common/index.mjs';
|
||||
|
||||
// Actual test is inside the loader module.
|
Loading…
Add table
Add a link
Reference in a new issue