conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

syntax.test.js (2377B)


      1 // test/syntax.test.js - every JavaScript file parses
      2 //
      3 // Most modules are covered because a test imports them, but entry points
      4 // and the dashboard are never imported by anything: the entry points start
      5 // listeners, and the dashboard only ever runs in a browser. A syntax error
      6 // in either would otherwise reach a deployment.
      7 //
      8 // node --check parses without executing, which is what makes this safe to
      9 // run over files that would start a server on import.
     10 
     11 import test from 'node:test';
     12 import assert from 'node:assert/strict';
     13 import fs from 'node:fs/promises';
     14 import path from 'node:path';
     15 import { execFile } from 'node:child_process';
     16 import { promisify } from 'node:util';
     17 import { fileURLToPath } from 'node:url';
     18 
     19 const execFileAsync = promisify(execFile);
     20 const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
     21 const SKIP = new Set(['.git', 'node_modules', 'data', 'tmp']);
     22 
     23 async function* walk(dir) {
     24   for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
     25     if (entry.isDirectory()) {
     26       if (SKIP.has(entry.name)) continue;
     27       yield* walk(path.join(dir, entry.name));
     28     } else if (entry.isFile() && entry.name.endsWith('.js')) {
     29       yield path.join(dir, entry.name);
     30     }
     31   }
     32 }
     33 
     34 test('every javascript file parses', async () => {
     35   const files = [];
     36   for await (const file of walk(ROOT)) files.push(file);
     37   assert.ok(files.length > 20, `expected to find source files, found ${files.length}`);
     38 
     39   const failures = [];
     40   // Batched, since each check is a process.
     41   for (let i = 0; i < files.length; i += 8) {
     42     const batch = files.slice(i, i + 8).map(async (file) => {
     43       try {
     44         await execFileAsync(process.execPath, ['--check', file], { timeout: 20000 });
     45       } catch (e) {
     46         failures.push(`${path.relative(ROOT, file)}: ${(e.stderr || e.message).trim().split('\n').slice(0, 3).join(' ')}`);
     47       }
     48     });
     49     await Promise.all(batch);
     50   }
     51 
     52   assert.deepEqual(failures, [], `files failed to parse:\n${failures.join('\n')}`);
     53 });
     54 
     55 test('the shell scripts parse', async () => {
     56   const failures = [];
     57   for (const script of ['hooks/post-receive']) {
     58     try {
     59       await execFileAsync('sh', ['-n', path.join(ROOT, script)], { timeout: 20000 });
     60     } catch (e) {
     61       failures.push(`${script}: ${(e.stderr || e.message).trim()}`);
     62     }
     63   }
     64   assert.deepEqual(failures, []);
     65 });