diff --git a/scripts/run-cross-platform-tests.cjs b/scripts/run-cross-platform-tests.cjs new file mode 100644 index 000000000..72eb3b1cd --- /dev/null +++ b/scripts/run-cross-platform-tests.cjs @@ -0,0 +1,63 @@ +'use strict'; + +const { spawnSync } = require('child_process'); + +const CROSS_PLATFORM_TEST_REASON = Object.freeze({ + PASS: 'pass', + TEST_FAILURE: 'test_failure', + INFRA_FAILURE: 'infra_failure', + UNKNOWN_FAILURE: 'unknown_failure', +}); + +function classify(exitCode, output) { + if (exitCode === 0) return CROSS_PLATFORM_TEST_REASON.PASS; + if (exitCode === 2 || /infrastructure failure|worktree\.Construct/i.test(output)) { + return CROSS_PLATFORM_TEST_REASON.INFRA_FAILURE; + } + if (/\bFAIL\b|\d+\s+failures?\)/i.test(output)) { + return CROSS_PLATFORM_TEST_REASON.TEST_FAILURE; + } + return CROSS_PLATFORM_TEST_REASON.UNKNOWN_FAILURE; +} + +function runCrossPlatformTests(options = {}, deps = {}) { + const { + base = 'next', + head = 'HEAD', + source = '.', + targets = 'linux,macos', + cwd = process.cwd(), + } = options; + const runner = deps.spawnSync || spawnSync; + + const args = ['--targets', targets, '--base', base, '--head', head, '--source', source]; + const result = runner('gsd-test', args, { cwd, encoding: 'utf8' }); + + const stdout = result.stdout || ''; + const stderr = result.stderr || ''; + const output = `${stdout}\n${stderr}`; + const exitCode = Number(result.status ?? 1); + const reason = classify(exitCode, output); + + return { + ok: exitCode === 0, + reason, + exitCode, + command: ['gsd-test', ...args].join(' '), + stdout, + stderr, + }; +} + +if (require.main === module) { + const result = runCrossPlatformTests(); + const line = `[cross-platform-tests] reason=${result.reason} exit=${result.exitCode}`; + if (result.ok) { + process.stdout.write(`${line}\n`); + process.exit(0); + } + process.stderr.write(`${line}\n`); + process.exit(result.exitCode); +} + +module.exports = { CROSS_PLATFORM_TEST_REASON, runCrossPlatformTests }; diff --git a/tests/run-cross-platform-tests.test.cjs b/tests/run-cross-platform-tests.test.cjs new file mode 100644 index 000000000..d55bb30a8 --- /dev/null +++ b/tests/run-cross-platform-tests.test.cjs @@ -0,0 +1,46 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); + +const { CROSS_PLATFORM_TEST_REASON, runCrossPlatformTests } = require('../scripts/run-cross-platform-tests.cjs'); + +describe('run cross-platform tests module', () => { + test('returns pass reason on zero exit', () => { + const out = runCrossPlatformTests({}, { + spawnSync: () => ({ status: 0, stdout: 'ok', stderr: '' }), + }); + assert.strictEqual(out.ok, true); + assert.strictEqual(out.reason, CROSS_PLATFORM_TEST_REASON.PASS); + assert.ok(out.command.includes('--base next')); + }); + + test('classifies infrastructure failure', () => { + const out = runCrossPlatformTests({}, { + spawnSync: () => ({ status: 2, stdout: '', stderr: 'infrastructure failure' }), + }); + assert.strictEqual(out.ok, false); + assert.strictEqual(out.reason, CROSS_PLATFORM_TEST_REASON.INFRA_FAILURE); + }); + + test('classifies test failure from FAIL marker', () => { + const out = runCrossPlatformTests({}, { + spawnSync: () => ({ status: 1, stdout: 'linux FAIL 1/2 tests (1 failures)', stderr: '' }), + }); + assert.strictEqual(out.ok, false); + assert.strictEqual(out.reason, CROSS_PLATFORM_TEST_REASON.TEST_FAILURE); + }); + + test('passes options through to command args', () => { + let cmd = null; + let args = null; + runCrossPlatformTests({ base: 'main', head: 'abc123', source: '/tmp/x', targets: 'linux' }, { + spawnSync: (c, a) => { + cmd = c; + args = a; + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + assert.strictEqual(cmd, 'gsd-test'); + assert.deepStrictEqual(args, ['--targets', 'linux', '--base', 'main', '--head', 'abc123', '--source', '/tmp/x']); + }); +});