| import '../common/index.mjs'; |
| import * as fixtures from '../common/fixtures.mjs'; |
| import { describe, it, beforeEach, run } from 'node:test'; |
| import assert from 'node:assert'; |
| import fs from 'node:fs'; |
| import tmpdir from '../common/tmpdir.js'; |
| import path from 'node:path'; |
| import { spawn } from 'node:child_process'; |
| import { once } from 'node:events'; |
| |
| const testFixtures = fixtures.path('test-runner', 'global-setup-teardown'); |
| const runnerFixture = fixtures.path('test-runner', 'test-runner-global-hooks.mjs'); |
| |
| describe('require(\'node:test\').run with global hooks', { concurrency: false }, () => { |
| beforeEach(() => { |
| tmpdir.refresh(); |
| }); |
| |
| async function runTestWithGlobalHooks({ |
| globalSetupFile, |
| testFile = 'test-file.js', |
| runnerEnv = {}, |
| isolation = 'process' |
| }) { |
| const testFilePath = path.join(testFixtures, testFile); |
| const globalSetupPath = path.join(testFixtures, globalSetupFile); |
| |
| const child = spawn( |
| process.execPath, |
| [ |
| runnerFixture, |
| '--file', testFilePath, |
| '--globalSetup', globalSetupPath, |
| '--isolation', isolation, |
| ], |
| { |
| encoding: 'utf8', |
| stdio: 'pipe', |
| env: { |
| ...runnerEnv, |
| ...process.env, |
| AVOID_PRINT_LOGS: 'true', |
| NODE_OPTIONS: '--no-warnings', |
| } |
| } |
| ); |
| |
| let stdout = ''; |
| let stderr = ''; |
| child.stdout.on('data', (data) => { |
| stdout += data.toString(); |
| }); |
| child.stderr.on('data', (data) => { |
| stderr += data.toString(); |
| }); |
| |
| await once(child, 'exit'); |
| |
| // Assert in order to print a detailed error message if the test fails |
| assert.partialDeepStrictEqual(stderr, ''); |
| assert.match(stdout, /pass (\d+)/); |
| assert.match(stdout, /fail (\d+)/); |
| |
| const results = { |
| passed: parseInt((stdout.match(/pass (\d+)/) || [])[1] || '0', 10), |
| failed: parseInt((stdout.match(/fail (\d+)/) || [])[1] || '0', 10) |
| }; |
| |
| return { results }; |
| } |
| |
| for (const isolation of ['none', 'process']) { |
| describe(`with isolation : ${isolation}`, () => { |
| it('should run globalSetup and globalTeardown functions', async () => { |
| const setupFlagPath = tmpdir.resolve('setup-executed.tmp'); |
| const teardownFlagPath = tmpdir.resolve('teardown-executed.tmp'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'basic-setup-teardown.js', |
| runnerEnv: { |
| SETUP_FLAG_PATH: setupFlagPath, |
| TEARDOWN_FLAG_PATH: teardownFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 2); |
| assert.strictEqual(results.failed, 0); |
| // After all tests complete, the teardown should have run |
| assert.ok(fs.existsSync(teardownFlagPath), 'Teardown flag file should exist'); |
| const content = fs.readFileSync(teardownFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Teardown was executed'); |
| // Setup flag should have been removed by teardown |
| assert.ok(!fs.existsSync(setupFlagPath), 'Setup flag file should have been removed'); |
| }); |
| |
| it('should run setup-only module', async () => { |
| const setupOnlyFlagPath = tmpdir.resolve('setup-only-executed.tmp'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'setup-only.js', |
| runnerEnv: { |
| SETUP_ONLY_FLAG_PATH: setupOnlyFlagPath, |
| SETUP_FLAG_PATH: setupOnlyFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 1); |
| assert.strictEqual(results.failed, 1); |
| assert.ok(fs.existsSync(setupOnlyFlagPath), 'Setup-only flag file should exist'); |
| const content = fs.readFileSync(setupOnlyFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Setup-only was executed'); |
| }); |
| |
| it('should run teardown-only module', async () => { |
| const teardownOnlyFlagPath = tmpdir.resolve('teardown-only-executed.tmp'); |
| const setupFlagPath = tmpdir.resolve('setup-for-teardown-only.tmp'); |
| |
| // Create a setup file for test-file.js to find |
| fs.writeFileSync(setupFlagPath, 'Setup was executed'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'teardown-only.js', |
| runnerEnv: { |
| TEARDOWN_ONLY_FLAG_PATH: teardownOnlyFlagPath, |
| SETUP_FLAG_PATH: setupFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 2); |
| assert.strictEqual(results.failed, 0); |
| assert.ok(fs.existsSync(teardownOnlyFlagPath), 'Teardown-only flag file should exist'); |
| const content = fs.readFileSync(teardownOnlyFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Teardown-only was executed'); |
| }); |
| |
| // TODO(pmarchini): We should be able to share context between setup and teardown |
| it.todo('should share context between setup and teardown'); |
| |
| it('should handle async setup and teardown', async () => { |
| const asyncFlagPath = tmpdir.resolve('async-executed.tmp'); |
| const setupFlagPath = tmpdir.resolve('setup-for-async.tmp'); |
| |
| // Create a setup file for test-file.js to find |
| fs.writeFileSync(setupFlagPath, 'Setup was executed'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'async-setup-teardown.js', |
| runnerEnv: { |
| ASYNC_FLAG_PATH: asyncFlagPath, |
| SETUP_FLAG_PATH: setupFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 2); |
| assert.strictEqual(results.failed, 0); |
| assert.ok(fs.existsSync(asyncFlagPath), 'Async flag file should exist'); |
| const content = fs.readFileSync(asyncFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Setup part, Teardown part'); |
| }); |
| |
| it('should run TypeScript globalSetup and globalTeardown functions', async () => { |
| const setupFlagPath = tmpdir.resolve('setup-executed-ts.tmp'); |
| const teardownFlagPath = tmpdir.resolve('teardown-executed-ts.tmp'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'basic-setup-teardown.ts', |
| runnerEnv: { |
| SETUP_FLAG_PATH: setupFlagPath, |
| TEARDOWN_FLAG_PATH: teardownFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 2); |
| assert.strictEqual(results.failed, 0); |
| // After all tests complete, the teardown should have run |
| assert.ok(fs.existsSync(teardownFlagPath), 'Teardown flag file should exist'); |
| const content = fs.readFileSync(teardownFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Teardown was executed'); |
| |
| // Setup flag should have been removed by teardown |
| assert.ok(!fs.existsSync(setupFlagPath), 'Setup flag file should have been removed'); |
| }); |
| |
| it('should run ESM globalSetup and globalTeardown functions', async () => { |
| const setupFlagPath = tmpdir.resolve('setup-executed-esm.tmp'); |
| const teardownFlagPath = tmpdir.resolve('teardown-executed-esm.tmp'); |
| |
| const { results } = await runTestWithGlobalHooks({ |
| globalSetupFile: 'basic-setup-teardown.mjs', |
| runnerEnv: { |
| SETUP_FLAG_PATH: setupFlagPath, |
| TEARDOWN_FLAG_PATH: teardownFlagPath |
| }, |
| isolation |
| }); |
| |
| assert.strictEqual(results.passed, 2); |
| assert.strictEqual(results.failed, 0); |
| // After all tests complete, the teardown should have run |
| assert.ok(fs.existsSync(teardownFlagPath), 'Teardown flag file should exist'); |
| const content = fs.readFileSync(teardownFlagPath, 'utf8'); |
| assert.strictEqual(content, 'Teardown was executed'); |
| // Setup flag should have been removed by teardown |
| assert.ok(!fs.existsSync(setupFlagPath), 'Setup flag file should have been removed'); |
| }); |
| }); |
| } |
| |
| it('should validate that globalSetupPath is a string', () => { |
| [123, {}, [], true, false].forEach((invalidValue) => { |
| assert.throws(() => { |
| run({ |
| files: [path.join(testFixtures, 'test-file.js')], |
| globalSetupPath: invalidValue |
| }); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| message: /The "options\.globalSetupPath" property must be of type string/ |
| }); |
| }); |
| }); |
| }); |