From 2d49e0e635625660f4dafbc080349cffd6c9e3da Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 31 Dec 2022 13:23:36 -0800 Subject: [PATCH] esm: rewrite loader hooks test Rewrite the test that validates that custom loader hooks are called from being a test that depends on internals to one that spawns a child process and checks its output to confirm expected behavior. PR-URL: https://github.com/nodejs/node/pull/46016 Reviewed-By: Antoine du Hamel Reviewed-By: Jacob Smith --- test/es-module/test-esm-loader-hooks.mjs | 105 ++++-------------- .../es-module-loaders/hooks-input.mjs | 91 +++++++++++++++ 2 files changed, 115 insertions(+), 81 deletions(-) create mode 100644 test/fixtures/es-module-loaders/hooks-input.mjs diff --git a/test/es-module/test-esm-loader-hooks.mjs b/test/es-module/test-esm-loader-hooks.mjs index 2704fe1a52ccce..7cd967189ec1fc 100644 --- a/test/es-module/test-esm-loader-hooks.mjs +++ b/test/es-module/test-esm-loader-hooks.mjs @@ -1,83 +1,26 @@ -// Flags: --expose-internals -import { mustCall } from '../common/index.mjs'; -import esmLoaderModule from 'internal/modules/esm/loader'; -import assert from 'assert'; - -const { ESMLoader } = esmLoaderModule; - -/** - * Verify custom hooks are called with appropriate arguments. - */ -{ - const esmLoader = new ESMLoader(); - - const originalSpecifier = 'foo/bar'; - const importAssertions = { - __proto__: null, - type: 'json', - }; - const parentURL = 'file:///entrypoint.js'; - const resolvedURL = 'file:///foo/bar.js'; - const suggestedFormat = 'test'; - - function resolve(specifier, context, defaultResolve) { - assert.strictEqual(specifier, originalSpecifier); - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Object.keys(context), [ - 'conditions', - 'importAssertions', - 'parentURL', - ]); - assert.ok(Array.isArray(context.conditions)); - assert.deepStrictEqual(context.importAssertions, importAssertions); - assert.strictEqual(context.parentURL, parentURL); - assert.strictEqual(typeof defaultResolve, 'function'); - - return { - format: suggestedFormat, - shortCircuit: true, - url: resolvedURL, - }; - } - - function load(resolvedURL, context, defaultLoad) { - assert.strictEqual(resolvedURL, resolvedURL); - assert.ok(new URL(resolvedURL)); - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Object.keys(context), [ - 'format', - 'importAssertions', +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', () => { + it('are called with all expected arguments', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ + '--no-warnings', + '--experimental-loader', + fixtures.fileURL('/es-module-loaders/hooks-input.mjs'), + fixtures.path('/es-modules/json-modules.mjs'), ]); - assert.strictEqual(context.format, suggestedFormat); - assert.deepStrictEqual(context.importAssertions, importAssertions); - assert.strictEqual(typeof defaultLoad, 'function'); - - // This doesn't matter (just to avoid errors) - return { - format: 'module', - shortCircuit: true, - source: '', - }; - } - - const customLoader = [ - { - exports: { - // Ensure ESMLoader actually calls the custom hooks - resolve: mustCall(resolve), - load: mustCall(load), - }, - url: import.meta.url, - }, - ]; - - esmLoader.addCustomLoaders(customLoader); - // Manually trigger hooks (since ESMLoader is not actually running) - const job = await esmLoader.getModuleJob( - originalSpecifier, - parentURL, - importAssertions, - ); - await job.modulePromise; -} + assert.strictEqual(stderr, ''); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + + const lines = stdout.split('\n'); + assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); + assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); + assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); + assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); + }); +}); diff --git a/test/fixtures/es-module-loaders/hooks-input.mjs b/test/fixtures/es-module-loaders/hooks-input.mjs new file mode 100644 index 00000000000000..6859cfc07d9b6a --- /dev/null +++ b/test/fixtures/es-module-loaders/hooks-input.mjs @@ -0,0 +1,91 @@ +// This is expected to be used by test-esm-loader-hooks.mjs via: +// node --loader ./test/fixtures/es-module-loaders/hooks-input.mjs ./test/fixtures/es-modules/json-modules.mjs + +import assert from 'assert'; +import { readFile } from 'fs/promises'; +import { fileURLToPath } from 'url'; + + +let resolveCalls = 0; +let loadCalls = 0; + +export async function resolve(specifier, context, next) { + resolveCalls++; + let url; + + if (resolveCalls === 1) { + url = new URL(specifier).href; + assert.match(specifier, /json-modules\.mjs$/); + assert.strictEqual(context.parentURL, undefined); + assert.deepStrictEqual(context.importAssertions, { + __proto__: null, + }); + } else if (resolveCalls === 2) { + url = new URL(specifier, context.parentURL).href; + assert.match(specifier, /experimental\.json$/); + assert.match(context.parentURL, /json-modules\.mjs$/); + assert.deepStrictEqual(context.importAssertions, { + __proto__: null, + type: 'json', + }); + } + + // Ensure `context` has all and only the properties it's supposed to + assert.deepStrictEqual(Reflect.ownKeys(context), [ + 'conditions', + 'importAssertions', + 'parentURL', + ]); + assert.ok(Array.isArray(context.conditions)); + assert.strictEqual(typeof next, 'function'); + + const returnValue = { + url, + format: 'test', + shortCircuit: true, + } + + console.log(JSON.stringify(returnValue)); // For the test to validate when it parses stdout + + return returnValue; +} + +export async function load(url, context, next) { + loadCalls++; + const source = await readFile(fileURLToPath(url)); + let format; + + if (loadCalls === 1) { + assert.match(url, /json-modules\.mjs$/); + assert.deepStrictEqual(context.importAssertions, { + __proto__: null, + }); + format = 'module'; + } else if (loadCalls === 2) { + assert.match(url, /experimental\.json$/); + assert.deepStrictEqual(context.importAssertions, { + __proto__: null, + type: 'json', + }); + format = 'json'; + } + + assert.ok(new URL(url)); + // Ensure `context` has all and only the properties it's supposed to + assert.deepStrictEqual(Object.keys(context), [ + 'format', + 'importAssertions', + ]); + assert.strictEqual(context.format, 'test'); + assert.strictEqual(typeof next, 'function'); + + const returnValue = { + source, + format, + shortCircuit: true, + }; + + console.log(JSON.stringify(returnValue)); // For the test to validate when it parses stdout + + return returnValue; +}