Skip to content

Commit

Permalink
test: CLI on all Node versions (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Nov 23, 2023
1 parent 5a71a87 commit 76cc728
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 78 deletions.
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const relaySignals = (

const waitForSignalFromChild = () => {
const p = new Promise<NodeJS.Signals | undefined>((resolve) => {
setTimeout(() => resolve(undefined), 10);
// Aribrary timeout based on flaky tests
setTimeout(() => resolve(undefined), 30);
waitForSignal = resolve;
});

Expand Down
9 changes: 6 additions & 3 deletions src/utils/node-features.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
type Version = [number, number, number];
export type Version = [number, number, number];

const nodeVersion = process.versions.node.split('.').map(Number) as Version;
const currentNodeVersion = process.versions.node.split('.').map(Number) as Version;

const compareNodeVersion = (version: Version) => (
export const compareNodeVersion = (
version: Version,
nodeVersion = currentNodeVersion,
) => (
nodeVersion[0] - version[0]
|| nodeVersion[1] - version[1]
|| nodeVersion[2] - version[2]
Expand Down
7 changes: 3 additions & 4 deletions tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { nodeVersions } from './utils/node-versions';

(async () => {
await describe('tsx', async ({ runTestSuite, describe }) => {
await runTestSuite(import('./specs/cli'));
await runTestSuite(import('./specs/watch'));
await runTestSuite(import('./specs/repl'));
await runTestSuite(import('./specs/transform'));

for (const nodeVersion of nodeVersions) {
const node = await createNode(nodeVersion);

await describe(`Node ${node.version}`, ({ runTestSuite }) => {
runTestSuite(
await describe(`Node ${node.version}`, async ({ runTestSuite }) => {
await runTestSuite(import('./specs/cli'), node);
await runTestSuite(
import('./specs/smoke'),
node,
);
Expand Down
140 changes: 70 additions & 70 deletions tests/specs/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { setTimeout } from 'timers/promises';
import { testSuite, expect } from 'manten';
import { createFixture } from 'fs-fixture';
import packageJson from '../../package.json';
import { tsx, tsxPath } from '../utils/tsx.js';
import { tsxPath } from '../utils/tsx.js';
import { ptyShell, isWindows } from '../utils/pty-shell/index';
import { expectMatchInOrder } from '../utils/expect-match-in-order.js';
import type { NodeApis } from '../utils/tsx.js';
import { compareNodeVersion, type Version } from '../../src/utils/node-features.js';

export default testSuite(({ describe }) => {
export default testSuite(({ describe }, node: NodeApis) => {
const { tsx } = node;
describe('CLI', ({ describe, test }) => {
describe('argv', async ({ describe, onFinish }) => {
const fixture = await createFixture({
Expand All @@ -18,22 +21,18 @@ export default testSuite(({ describe }) => {

describe('version', ({ test }) => {
test('shows version', async () => {
const tsxProcess = await tsx({
args: ['--version'],
});
const tsxProcess = await tsx(['--version']);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toBe(`tsx v${packageJson.version}\nnode ${process.version}`);
expect(tsxProcess.stdout).toBe(`tsx v${packageJson.version}\nnode v${node.version}`);
expect(tsxProcess.stderr).toBe('');
});

test('doesn\'t show version with file', async () => {
const tsxProcess = await tsx({
args: [
path.join(fixture.path, 'log-argv.ts'),
'--version',
],
});
const tsxProcess = await tsx([
path.join(fixture.path, 'log-argv.ts'),
'--version',
]);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch('"--version"');
Expand All @@ -44,9 +43,7 @@ export default testSuite(({ describe }) => {

describe('help', ({ test }) => {
test('shows help', async () => {
const tsxProcess = await tsx({
args: ['--help'],
});
const tsxProcess = await tsx(['--help']);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch('Node.js runtime enhanced with esbuild for loading TypeScript & ESM');
Expand All @@ -55,12 +52,10 @@ export default testSuite(({ describe }) => {
});

test('doesn\'t show help with file', async () => {
const tsxProcess = await tsx({
args: [
path.join(fixture.path, 'log-argv.ts'),
'--help',
],
});
const tsxProcess = await tsx([
path.join(fixture.path, 'log-argv.ts'),
'--help',
]);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch('"--help"');
Expand All @@ -72,61 +67,72 @@ export default testSuite(({ describe }) => {

describe('eval & print', ({ test }) => {
test('TypeScript', async () => {
const tsxProcess = await tsx({
args: ['--eval', 'console.log(require("fs") && module as string)'],
});
const tsxProcess = await tsx([
'--eval',
'console.log(require("fs") && module as string)',
]);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch("id: '[eval]'");
expect(tsxProcess.stderr).toBe('');
});

test('--input-type=module is respected', async () => {
const tsxProcess = await tsx({
args: ['--eval', 'console.log(JSON.stringify([typeof require, import.meta.url]))', '--input-type=module'],
});
const tsxProcess = await tsx([
'--input-type=module',
'--eval',
'console.log(typeof require)',
]);

expect(tsxProcess.exitCode).toBe(0);
const [requireDefined, importMetaUrl] = JSON.parse(tsxProcess.stdout);
expect(requireDefined).toBe('undefined');
expect(importMetaUrl.endsWith('/[eval1]')).toBeTruthy();
expect(tsxProcess.stdout).toBe('undefined');
expect(tsxProcess.stderr).toBe('');
});

test('--print', async () => {
const tsxProcess = await tsx({
args: ['--print', 'require("fs") && module as string'],
});
const tsxProcess = await tsx([
'--print',
'require("fs") && module as string',
]);

expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch("id: '[eval]'");
expect(tsxProcess.stderr).toBe('');
});
});

test('Node.js test runner', async ({ onTestFinish }) => {
const fixture = await createFixture({
'test.ts': `
import { test } from 'node:test';
import assert from 'assert';
test('passing test', () => {
assert.strictEqual(1, 1);
if (
compareNodeVersion(
// https://nodejs.org/docs/latest-v18.x/api/cli.html#--test
[18, 1, 0],
node.version.split('.').map(Number) as Version,
) >= 0
) {
test('Node.js test runner', async ({ onTestFinish }) => {
const fixture = await createFixture({
'test.ts': `
import { test } from 'node:test';
import assert from 'assert';
test('passing test', () => {
assert.strictEqual(1, 1);
});
`,
});
`,
});
onTestFinish(async () => await fixture.rm());
onTestFinish(async () => await fixture.rm());

const tsxProcess = await tsx({
args: [
'--test',
path.join(fixture.path, 'test.ts'),
],
});
const tsxProcess = await tsx(
[
'--test',
'test.ts',
],
fixture.path,
);

expect(tsxProcess.stdout).toMatch('# pass 1\n');
expect(tsxProcess.exitCode).toBe(0);
}, 10_000);
expect(tsxProcess.exitCode).toBe(0);
expect(tsxProcess.stdout).toMatch('# pass 1\n');
}, 10_000);
}

describe('Signals', async ({ describe, onFinish }) => {
const signals = ['SIGINT', 'SIGTERM'];
Expand Down Expand Up @@ -172,11 +178,9 @@ export default testSuite(({ describe }) => {
describe('Relays kill signal', ({ test }) => {
for (const signal of signals) {
test(signal, async ({ onTestFail }) => {
const tsxProcess = tsx({
args: [
path.join(fixture.path, 'catch-signals.js'),
],
});
const tsxProcess = tsx([
path.join(fixture.path, 'catch-signals.js'),
]);

tsxProcess.stdout!.once('data', () => {
tsxProcess.kill(signal, {
Expand Down Expand Up @@ -213,11 +217,9 @@ export default testSuite(({ describe }) => {
});

test('Kills child when unresponsive (infinite loop)', async () => {
const tsxProcess = tsx({
args: [
path.join(fixture.path, 'infinite-loop.js'),
],
});
const tsxProcess = tsx([
path.join(fixture.path, 'infinite-loop.js'),
]);

const childPid = await new Promise<number>((resolve) => {
tsxProcess.stdout!.once('data', (data) => {
Expand All @@ -237,11 +239,9 @@ export default testSuite(({ describe }) => {
}, 10_000);

test('Doesn\'t kill child when responsive (ignores signal)', async () => {
const tsxProcess = tsx({
args: [
path.join(fixture.path, 'ignores-signals.js'),
],
});
const tsxProcess = tsx([
path.join(fixture.path, 'ignores-signals.js'),
]);

const childPid = await new Promise<number>((resolve) => {
tsxProcess.stdout!.once('data', (data) => {
Expand Down Expand Up @@ -270,7 +270,7 @@ export default testSuite(({ describe }) => {
test('Exit code', async () => {
const output = await ptyShell(
[
`${process.execPath} ${tsxPath} ${path.join(fixture.path, 'keep-alive.js')}\r`,
`${node.path} ${tsxPath} ${path.join(fixture.path, 'keep-alive.js')}\r`,
stdout => stdout.includes('READY') && '\u0003',
`echo EXIT_CODE: ${isWindows ? '$LastExitCode' : '$?'}\r`,
],
Expand All @@ -281,7 +281,7 @@ export default testSuite(({ describe }) => {
test('Catchable', async () => {
const output = await ptyShell(
[
`${process.execPath} ${tsxPath} ${path.join(fixture.path, 'catch-signals.js')}\r`,
`${node.path} ${tsxPath} ${path.join(fixture.path, 'catch-signals.js')}\r`,
stdout => stdout.includes('READY') && '\u0003',
`echo EXIT_CODE: ${isWindows ? '$LastExitCode' : '$?'}\r`,
],
Expand Down
1 change: 1 addition & 0 deletions tests/utils/tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const createNode = async (

return {
version: node.version,
path: node.path,
tsx: (
args: string[],
cwd?: string,
Expand Down

0 comments on commit 76cc728

Please sign in to comment.