@ 261106.2
current
'use strict';

require('../common');
const assert = require('node:assert');
const { spawnSync } = require('node:child_process');
const { join } = require('node:path');
const { describe, it } = require('node:test');
const fixtures = require('../common/fixtures');

const testFixtures = fixtures.path('test-runner');
const internalOrderFixture = fixtures.path('test-runner', 'randomize', 'internal-order.cjs');
const rerunStateFile = fixtures.path('test-runner', 'rerun-state.json');
const kShardFiles = ['a.cjs', 'b.cjs', 'c.cjs', 'd.cjs', 'e.cjs', 'f.cjs', 'g.cjs', 'h.cjs', 'i.cjs', 'j.cjs'];
const kInternalTests = ['a', 'b', 'c', 'd', 'e'];

function getShardOrder(stdout) {
  return Array.from(stdout.matchAll(/ok \d+ - ([a-j]\.cjs) this should pass/g), ({ 1: name }) => name);
}

function getInternalExecutionOrder(stdout) {
  const match = stdout.match(/EXECUTION_ORDER:([a-e](?:,[a-e])*)/);
  assert(match, `Missing EXECUTION_ORDER marker in output: ${stdout}`);
  return match[1].split(',');
}

describe('test runner randomize flags via command line', () => {
  it('should be deterministic with --test-random-seed', () => {
    const args = [
      '--test',
      '--test-reporter=tap',
      '--test-concurrency=1',
      '--test-randomize',
      '--test-random-seed=12345',
      join(testFixtures, 'shards/*.cjs'),
    ];
    const first = spawnSync(process.execPath, args);
    const second = spawnSync(process.execPath, args);

    assert.strictEqual(first.stderr.toString(), '');
    assert.strictEqual(second.stderr.toString(), '');
    assert.strictEqual(first.status, 0);
    assert.strictEqual(second.status, 0);

    const firstOrder = getShardOrder(first.stdout.toString());
    const secondOrder = getShardOrder(second.stdout.toString());
    assert.deepStrictEqual(firstOrder, secondOrder);
    assert.deepStrictEqual([...firstOrder].sort(), kShardFiles);
  });

  it('should use different orders for different seeds', () => {
    const first = spawnSync(process.execPath, [
      '--test',
      '--test-reporter=tap',
      '--test-concurrency=1',
      '--test-randomize',
      '--test-random-seed=11111',
      join(testFixtures, 'shards/*.cjs'),
    ]);
    const second = spawnSync(process.execPath, [
      '--test',
      '--test-reporter=tap',
      '--test-concurrency=1',
      '--test-randomize',
      '--test-random-seed=22222',
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(first.stderr.toString(), '');
    assert.strictEqual(second.stderr.toString(), '');
    assert.strictEqual(first.status, 0);
    assert.strictEqual(second.status, 0);

    const firstOrder = getShardOrder(first.stdout.toString());
    const secondOrder = getShardOrder(second.stdout.toString());
    assert.notDeepStrictEqual(firstOrder, secondOrder);
    assert.deepStrictEqual([...firstOrder].sort(), kShardFiles);
    assert.deepStrictEqual([...secondOrder].sort(), kShardFiles);
  });

  it('should randomize deterministically with --test-random-seed alone', () => {
    const args = [
      '--test',
      '--test-reporter=tap',
      '--test-concurrency=1',
      '--test-random-seed=24680',
      join(testFixtures, 'shards/*.cjs'),
    ];
    const first = spawnSync(process.execPath, args);
    const second = spawnSync(process.execPath, args);

    assert.strictEqual(first.stderr.toString(), '');
    assert.strictEqual(second.stderr.toString(), '');
    assert.strictEqual(first.status, 0);
    assert.strictEqual(second.status, 0);

    const firstOrder = getShardOrder(first.stdout.toString());
    const secondOrder = getShardOrder(second.stdout.toString());
    assert.deepStrictEqual(firstOrder, secondOrder);
    assert.deepStrictEqual([...firstOrder].sort(), kShardFiles);
    assert.match(first.stdout.toString(), /# Randomized test order seed: 24680/);
  });

  it('should print the randomization seed when --test-randomize is used', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--test-reporter=tap',
      '--test-concurrency=1',
      '--test-randomize',
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stderr.toString(), '');
    assert.strictEqual(child.status, 0);
    assert.match(child.stdout.toString(), /# Randomized test order seed: \d+/);
  });

  it('should randomize internal test order deterministically with --test-random-seed', () => {
    const args = [
      '--test',
      '--test-reporter=tap',
      '--test-random-seed=12345',
      internalOrderFixture,
    ];
    const first = spawnSync(process.execPath, args);
    const second = spawnSync(process.execPath, args);

    assert.strictEqual(first.stderr.toString(), '');
    assert.strictEqual(second.stderr.toString(), '');
    assert.strictEqual(first.status, 0);
    assert.strictEqual(second.status, 0);

    const firstOrder = getInternalExecutionOrder(first.stdout.toString());
    const secondOrder = getInternalExecutionOrder(second.stdout.toString());
    assert.deepStrictEqual(firstOrder, secondOrder);
    assert.deepStrictEqual([...firstOrder].sort(), kInternalTests);
  });

  it('should randomize internal test order differently across seeds', () => {
    const orders = [];
    for (const seed of [11111, 22222, 33333, 44444]) {
      const child = spawnSync(process.execPath, [
        '--test',
        '--test-reporter=tap',
        `--test-random-seed=${seed}`,
        internalOrderFixture,
      ]);
      assert.strictEqual(child.stderr.toString(), '');
      assert.strictEqual(child.status, 0);

      const order = getInternalExecutionOrder(child.stdout.toString());
      assert.deepStrictEqual([...order].sort(), kInternalTests);
      orders.push(order.join(','));
    }

    assert.notStrictEqual(new Set(orders).size, 1);
  });

  it('should reject --test-randomize with --watch', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--watch',
      '--test-randomize',
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stdout.toString(), '');
    assert.match(child.stderr.toString(), /The property 'options\.randomize' is not supported with watch mode\./);
    assert.strictEqual(child.status, 1);
    assert.strictEqual(child.signal, null);
  });

  it('should reject --test-random-seed with --watch', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--watch',
      '--test-random-seed=12345',
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stdout.toString(), '');
    assert.match(child.stderr.toString(), /The property 'options\.randomSeed' is not supported with watch mode\./);
    assert.strictEqual(child.status, 1);
    assert.strictEqual(child.signal, null);
  });

  it('should reject --test-randomize with --test-rerun-failures', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--test-randomize',
      '--test-rerun-failures',
      rerunStateFile,
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stdout.toString(), '');
    assert.match(child.stderr.toString(), /The property 'options\.randomize' is not supported with rerun failures mode\./);
    assert.strictEqual(child.status, 1);
    assert.strictEqual(child.signal, null);
  });

  it('should reject --test-random-seed with --test-rerun-failures', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--test-random-seed=12345',
      '--test-rerun-failures',
      rerunStateFile,
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stdout.toString(), '');
    assert.match(child.stderr.toString(), /The property 'options\.randomSeed' is not supported with rerun failures mode\./);
    assert.strictEqual(child.status, 1);
    assert.strictEqual(child.signal, null);
  });

  it('should reject out of range --test-random-seed values', () => {
    const child = spawnSync(process.execPath, [
      '--test',
      '--test-random-seed=4294967296',
      join(testFixtures, 'shards/*.cjs'),
    ]);

    assert.strictEqual(child.stdout.toString(), '');
    assert.match(child.stderr.toString(), /The value of "--test-random-seed" is out of range\. It must be >= 0 && <= 4294967295\. Received 4294967296/);
    assert.strictEqual(child.status, 1);
    assert.strictEqual(child.signal, null);
  });
});