UNPKG

@gullerya/just-test

Version:

JavaScript multiplatform tests runner

177 lines (157 loc) 5.88 kB
import os from 'node:os'; import fs from 'node:fs/promises'; import process from 'node:process'; import { start, stop } from './server/cli.js'; import { xUnitReporter } from './testing/testing-service.js'; import { collectTargetSources, lcovReporter } from './coverage/coverage-service.js'; import { buildJTFileCov } from './coverage/model/model-utils.js'; go(); const SESSION_STATUS_POLL_INTERVAL = 137; async function go() { const startTime = globalThis.performance.now(); const clArguments = parseCLArgs(process.argv); console.info(`Starting local run...`); console.info(`${'='.repeat(64)}${os.EOL}`); let server; let sessionResult; let endedWithFailure = false; try { // TODO: spawn out the server in a separate process server = await start(); sessionResult = await executeSession(server.baseUrl, clArguments); endedWithFailure = !sessionResult.summary.success; } catch (error) { console.error(os.EOL); console.error(error); console.error(os.EOL); endedWithFailure = true; } finally { if (server && server.isRunning) { await stop(); } const duration = ((globalThis.performance.now() - startTime) / 1000).toFixed(1); console.info(`${os.EOL}${'='.repeat(64)}`); console.info(`... local run finished${os.EOL}`); if (sessionResult) { console.info('TESTS SUMMARY'); console.info('============='); console.info(`TOTAL: ${sessionResult.total}`); console.info(`PASSED: ${sessionResult.pass}`); console.info(`FAILED: ${sessionResult.fail}`); console.info(`ERRORED: ${sessionResult.error}`); console.info(`SKIPPED: ${sessionResult.skip}${os.EOL}`); console.info(`SESSION SUMMARY: ${endedWithFailure ? 'FAILURE' : 'SUCCESS'} (${duration}s)${os.EOL}`); process.exit(endedWithFailure ? 1 : 0); } else { console.info(`SESSION SUMMARY: FAILURE (${duration}s), see errors in the log above${os.EOL}`); process.exit(1); } } } function parseCLArgs(args) { const result = {}; if (Array.isArray(args)) { for (let i = 0; i < args.length; i++) { if (args[i].includes('=')) { const [key, val] = args[i].split('='); if (key in result) { throw new Error(`duplicate key '${key}'`); } result[key] = val; } } } return result; } async function executeSession(serverBaseUrl, clArguments) { const config = await readConfigAndMergeWithCLArguments(clArguments); const sessionDetails = await sendAddSession(serverBaseUrl, config); const sessionResult = await waitSessionEnd(serverBaseUrl, sessionDetails); // test report const reportText = xUnitReporter.report(sessionResult); await fs.writeFile('reports/results.xml', reportText, { encoding: 'utf-8' }); // coverage report const testCoverages = sessionResult.suites .flatMap(s => s.tests) .map(t => { return t && t.lastRun && t.lastRun.coverage ? { testId: t.name, coverage: t.lastRun.coverage } : null; }) .filter(Boolean); const targetSources = await collectTargetSources(config.environments[0].coverage); const fileCoverages = await Promise.all( targetSources .filter(ts => { return !testCoverages .flatMap(tc => tc.coverage) .some(fc => fc.url === ts); }) .map(ts => buildJTFileCov(ts, false)) ); const covContent = lcovReporter.convert({ testCoverages, fileCoverages }); await fs.rm('reports/coverage.lcov', { force: true, recursive: true }); if (covContent) { await fs.writeFile('reports/coverage.lcov', covContent, { encoding: 'utf-8', recursive: true }); } // analysis sessionResult.summary = { success: true, failReason: null }; const maxFail = config.environments[0].tests.maxFail; const maxSkip = config.environments[0].tests.maxSkip; if ((sessionResult.fail + sessionResult.error) > maxFail) { sessionResult.summary.success = false; sessionResult.summary.failReason = `failing due to too many failures/errors; max allowed: ${maxFail}, found: ${sessionResult.fail + sessionResult.error}`; } else if (sessionResult.skip > maxSkip) { sessionResult.summary.success = false; sessionResult.summary.failReason = `failing due to too many skipped; max allowed: ${maxSkip}, found: ${sessionResult.skip}`; } return sessionResult; } async function readConfigAndMergeWithCLArguments(clArguments) { if (!clArguments || !clArguments.config_file || typeof clArguments.config_file !== 'string') { throw new Error(`invalid config_file argument (${clArguments?.config_file})`); } const configText = await fs.readFile(clArguments.config_file, { encoding: 'utf-8' }); const result = JSON.parse(configText); // merge with command line arguments return result; } async function sendAddSession(serverBaseUrl, config) { const addSessionUrl = `${serverBaseUrl}/api/v1/sessions`; const options = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }; const response = await fetch(addSessionUrl, options); if (response.status !== 201) { throw new Error(`failed to create session; status: ${response.status}, message: ${response.statusText}`); } else { return await response.json(); } } async function waitSessionEnd(serverBaseUrl, sessionDetails) { const sessionResultUrl = `${serverBaseUrl}/api/v1/sessions/${sessionDetails.sessionId}/result`; // TODO: add global timeout return new Promise(resolve => { const p = async () => { const response = await fetch(sessionResultUrl); if (response.status === 200) { resolve(await response.json()); } else if (response.status === 204) { setTimeout(p, SESSION_STATUS_POLL_INTERVAL); } else { throw new Error(`failed to obtain session status; status: ${response.status}, message: ${response.statusText}`); } }; p(); }); }