Skip to content

Commit a7aa440

Browse files
cspotcodeisaacs
authored andcommitted
WIP fix tests for off-thread loader
1 parent 8b4be1d commit a7aa440

File tree

10 files changed

+119
-35
lines changed

10 files changed

+119
-35
lines changed

ava.config.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = {
1616
CONCURRENT_TESTS: '4'
1717
},
1818
require: ['./src/test/remove-env-var-force-color.js'],
19-
nodeArguments: ['--loader', './src/test/test-loader.mjs', '--no-warnings'],
19+
nodeArguments: ['--loader', './src/test/test-loader/loader.mjs', '--no-warnings'],
2020
timeout: '300s',
2121
concurrency: 4,
2222
// We do chdir -- maybe other things -- that you can't do in worker_threads.

src/test/helpers/ctx-ts-node.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { testsDirRequire, tsNodeTypes } from './misc';
1111
/** Pass to `test.context()` to get access to the ts-node API under test */
1212
export async function ctxTsNode() {
1313
await installTsNode();
14+
const tsNodeSpecifier = testsDirRequire.resolve('ts-node');
1415
const tsNodeUnderTest: typeof tsNodeTypes = testsDirRequire('ts-node');
1516
return {
17+
tsNodeSpecifier,
1618
tsNodeUnderTest,
1719
};
1820
}

src/test/helpers/reset-node-environment.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { has, mapValues, sortBy } from 'lodash';
2+
import * as testLoader from '../test-loader/client';
23

34
// Reset node environment
45
// Useful because ts-node installation necessarily must mutate the node environment.
@@ -44,7 +45,7 @@ export function resetNodeEnvironment() {
4445
resetObject(global, defaultGlobal, ['__coverage__']);
4546

4647
// Reset our ESM hooks
47-
process.__test_setloader__?.(undefined);
48+
testLoader.clearLoader();
4849
}
4950

5051
function captureObjectState(object: any, avoidGetters: string[] = []) {

src/test/repl/repl.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ test.suite('top level await', ({ contextEach }) => {
222222
});
223223

224224
test('should pass upstream test cases', async (t) => {
225-
const { tsNodeUnderTest } = t.context;
226-
await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest });
225+
const { tsNodeUnderTest, tsNodeSpecifier } = t.context;
226+
await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest, tsNodeSpecifier });
227227
});
228228
});
229229

src/test/resolver.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import * as semver from 'semver';
1313
import { padStart } from 'lodash';
1414
import _ = require('lodash');
1515
import { pathToFileURL } from 'url';
16-
import type { RegisterOptions } from '..';
16+
import type { CreateOptions, RegisterOptions } from '..';
1717
import * as fs from 'fs';
1818
import * as Path from 'path';
19+
import * as testLoader from './test-loader/client';
1920

2021
/*
2122
* Each test case is a separate TS project, with a different permutation of
@@ -660,10 +661,11 @@ async function execute(t: T, p: FsProject, entrypoints: Entrypoint[]) {
660661
// Install ts-node and try to import all the index-* files
661662
//
662663

663-
const service = t.context.tsNodeUnderTest.register({
664+
const options: CreateOptions = {
664665
projectSearchDir: p.cwd,
665-
});
666-
process.__test_setloader__(t.context.tsNodeUnderTest.createEsmHooks(service));
666+
};
667+
t.context.tsNodeUnderTest.register(options);
668+
await testLoader.setLoader(t.context.tsNodeSpecifier, options);
667669

668670
for (const entrypoint of entrypoints) {
669671
t.log(`Importing ${join(p.cwd, entrypoint)}`);

src/test/test-loader.d.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/test/test-loader.mjs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/test/test-loader/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
An injectable --loader used by tests. It allows tests to setup and teardown
2+
different loader behaviors in-process.
3+
4+
By default, it does nothing, as if we were not using a loader at all. But it
5+
responds to specially-crafted `import`s, installing or uninstalling a loader at
6+
runtime.
7+
8+
See also `ava.config.cjs`
9+
10+
The loader is implemented in `loader.mjs`, and functions to send it commands are
11+
in `client.ts`.

src/test/test-loader/client.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { URL } from 'url';
2+
import type { CreateOptions } from '../..';
3+
4+
// Duplicated in loader.mjs
5+
const protocol = 'testloader://';
6+
const clearLoaderCmd = 'clearLoader';
7+
const setLoaderCmd = 'setLoader';
8+
9+
// Avoid ts compiler transforming import() into require().
10+
const doImport = new Function('specifier', 'return import(specifier)');
11+
let cacheBust = 0;
12+
async function call(url: URL) {
13+
url.searchParams.set('cacheBust', `${cacheBust++}`);
14+
await doImport(url.toString());
15+
}
16+
17+
export async function clearLoader() {
18+
await call(new URL(`${protocol}${clearLoaderCmd}`));
19+
}
20+
21+
export async function setLoader(specifier: string, options: CreateOptions | undefined) {
22+
const url = new URL(`${protocol}${setLoaderCmd}`);
23+
url.searchParams.append('specifier', specifier);
24+
url.searchParams.append('options', JSON.stringify(options));
25+
await call(url);
26+
}

src/test/test-loader/loader.mjs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import semver from 'semver';
2+
import { URL } from 'url';
3+
import { createRequire } from 'module';
4+
5+
export const protocol = 'testloader://';
6+
7+
const newHooksAPI = semver.gte(process.versions.node, '16.12.0');
8+
9+
let hooks = undefined;
10+
11+
const require = createRequire(import.meta.url);
12+
13+
//
14+
// Commands
15+
//
16+
17+
export const clearLoaderCmd = 'clearLoader';
18+
function clearLoader() {
19+
hooks = undefined;
20+
}
21+
22+
export const setLoaderCmd = 'setLoader';
23+
function setLoader(specifier, options) {
24+
const tsNode = require(specifier);
25+
const service = tsNode.create(options);
26+
hooks = tsNode.createEsmHooks(service);
27+
}
28+
29+
//
30+
// Loader hooks
31+
//
32+
33+
function createHook(name) {
34+
return function (a, b, c) {
35+
const target = (hooks && hooks[name]) || c;
36+
return target(...arguments);
37+
};
38+
}
39+
40+
export const empty = `${protocol}empty`;
41+
const resolveEmpty = { url: empty, shortCircuit: true };
42+
43+
const _resolve = createHook('resolve');
44+
export function resolve(specifier, ...rest) {
45+
if (specifier.startsWith(protocol)) {
46+
const url = new URL(specifier);
47+
switch (url.host) {
48+
case setLoaderCmd:
49+
const specifier = url.searchParams.get('specifier');
50+
const options = JSON.parse(url.searchParams.get('options'));
51+
setLoader(specifier, options);
52+
return resolveEmpty;
53+
case clearLoaderCmd:
54+
clearLoader();
55+
return resolveEmpty;
56+
}
57+
}
58+
return _resolve(specifier, ...rest);
59+
}
60+
61+
const _loadHook = newHooksAPI ? createHook('load') : null;
62+
function _load(url, ...rest) {
63+
if (url === empty) return { format: 'module', source: '', shortCircuit: true };
64+
return _loadHook(url, ...rest);
65+
}
66+
export const load = newHooksAPI ? _load : null;
67+
68+
export const getFormat = !newHooksAPI ? createHook('getFormat') : null;
69+
export const transformSource = !newHooksAPI ? createHook('transformSource') : null;

0 commit comments

Comments
 (0)