Skip to content

Commit

Permalink
Reworked CI sub-checks
Browse files Browse the repository at this point in the history
  • Loading branch information
james-pre committed Jan 17, 2025
1 parent 4e90dfe commit ecda663
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 57 deletions.
20 changes: 10 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,34 @@ jobs:
run: npm install

- name: Create checks
run: npx zenfs-ci --create Formatting Build Linting "Unit tests (common)" "Unit tests (InMemory)" "Unit tests (contexts)" "Unit tests (Index)" "Unit tests (Port)" "Unit tests (Overlay+Fetch)"
run: npx zci init

- name: Formatting
run: npx zenfs-ci Formatting -R "npm run format:check"
run: npx zci run format "npm run format:check"

- name: Build
run: npx zenfs-ci Build -R "npm run build"
run: npx zci run build "npm run build"

- name: Linting
run: npx zenfs-ci Linting -R "npm run lint"
run: npx zci run lint "npm run lint"

- name: Unit tests (common)
run: npx zenfs-test -pfw --common --ci "Unit tests (common)"
run: npx zenfs-test --ci -pfw --common

- name: Unit tests (InMemory)
run: npx zenfs-test -pfw tests/setup/memory.ts --ci "Unit tests (InMemory)"
run: npx zenfs-test --ci -pfw tests/setup/memory.ts

- name: Unit tests (contexts)
run: npx zenfs-test -pfw tests/setup/context.ts --ci "Unit tests (contexts)"
run: npx zenfs-test --ci -pfw tests/setup/context.ts

- name: Unit tests (Index)
run: npx zenfs-test -pfw tests/setup/index.ts --ci "Unit tests (Index)"
run: npx zenfs-test --ci -pfw tests/setup/index.ts

- name: Unit tests (Port)
run: npx zenfs-test -pfw tests/setup/port.ts --ci "Unit tests (Port)"
run: npx zenfs-test --ci -pfw tests/setup/port.ts

- name: Unit tests (Overlay+Fetch)
run: tests/fetch/run.sh -w --ci "Unit tests (Overlay+Fetch)"
run: tests/fetch/run.sh --ci -w

- name: Report coverage
run: npx zenfs-test --report
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"bin": {
"make-index": "scripts/make-index.js",
"zenfs-test": "scripts/test.js",
"zenfs-ci": "scripts/ci-cli.js"
"zci": "scripts/ci-cli.js"
},
"files": [
"dist",
Expand Down
60 changes: 34 additions & 26 deletions scripts/ci-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,52 @@ import { spawnSync } from 'node:child_process';
import { parseArgs } from 'node:util';
import * as ci from './ci.js';

const { values: options, positionals } = parseArgs({
const {
values: options,
positionals: [subcommand, ...args],
} = parseArgs({
options: {
create: { type: 'boolean', default: false },
start: { type: 'boolean', default: false },
complete: { type: 'string' },
help: { short: 'h', type: 'boolean', default: false },
exit: { short: 'e', type: 'boolean', default: false },
run: { short: 'R', type: 'string' },
},
allowPositionals: true,
});

if (options.help) {
console.log(`Usage: zenfs-ci [options] <check names...>
Options:
-R, --run <command> Run a command and use that for statuses. Implies --exit
--create Create check(s) in a queued state for all positional check names
--start Move check(s) from queued to in_progress
--complete <status> Complete the check(s) with a conclusion: success, failure, etc.
-e, --exit Set the exit code based on the status of --complete
-h, --help Show this help message`);
if (options.help || !subcommand) {
console.log(`Usage:
zci [-h | --help] Show this help message
zci init Create checks in a queued state
zci run <name> <cmd> Run a command and use its exit code as the completion status
zci list List checks`);
process.exit();
}

for (const name of positionals) {
if (options.create) await ci.createCheck(name);
if (options.start) await ci.startCheck(name);
if (options.complete) await ci.completeCheck(name, options.complete);
if (options.complete && options.exit) process.exitCode = +(options.complete == 'failure');
if (options.run) {
const ciNames = Object.entries(ci.checkNames);

switch (subcommand) {
case 'init':
for (const [id, name] of ciNames) {
await ci.createCheck(id, name);
}
break;
case 'list': {
const max = Math.max(...ciNames.map(([id]) => id.length)) + 1;
for (const [id, name] of ciNames) {
console.log(id.padEnd(max), name);
}
break;
}
case 'run': {
const [name, ...command] = args;

await ci.startCheck(name);

const result = spawnSync(options.run, { shell: true, stdio: 'inherit' });
const exitCode = result.status;
const { status } = spawnSync(command.join(' '), { shell: true, stdio: 'inherit' });

await ci.completeCheck(name, exitCode ? 'failure' : 'success');
await ci.completeCheck(name, status ? 'failure' : 'success');

if (!exitCode) process.exitCode = exitCode;
process.exit(status);
}
default:
console.error('Unknown subcommand:', subcommand);
process.exit(1);
}
34 changes: 27 additions & 7 deletions scripts/ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,25 @@ const head_sha = process.env.GITHUB_SHA;

mkdirSync(join(import.meta.dirname, '../tmp'), { recursive: true });

const runIDs = new JSONFileMap(join(import.meta.dirname, '../tmp/checks.json'));
const checks = new JSONFileMap(join(import.meta.dirname, '../tmp/checks.json'));

/** Maps test names and shortcuts to full check names */
export const checkNames = {
// Basic ones
format: 'Formatting',
lint: 'Linting',
build: 'Build',
// Tests
'Common Tests': 'Unit tests (common)',
memory: 'Unit tests (InMemory)',
context: 'Unit tests (contexts)',
index: 'Unit tests (Index)',
port: 'Unit tests (Port)',
'cow+fetch': 'Unit tests (Overlay, Fetch)',
};

/** Create a new GitHub check run */
export async function createCheck(name) {
export async function createCheck(id, name) {
const response = await octokit.request('POST /repos/{owner}/{repo}/check-runs', {
owner,
repo,
Expand All @@ -23,28 +38,33 @@ export async function createCheck(name) {
started_at: new Date().toISOString(),
});

runIDs.set(name, response.data.id);
checks.set(id, { id: response.data.id, completed: false });
}

/**
* Move an existing check run from "queued" to "in_progress".
*/
export async function startCheck(name) {
export async function startCheck(id) {
const check = checks.get(id);

await octokit.request('PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}', {
owner,
repo,
check_run_id: runIDs.get(name),
check_run_id: check.id,
status: 'in_progress',
started_at: new Date().toISOString(),
});
}

/** Complete a check run */
export async function completeCheck(name, conclusion, title = '', summary = '') {
export async function completeCheck(id, conclusion, title = '', summary = '') {
const check = checks.get(id);
if (check.completed) return;

await octokit.request('PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}', {
owner,
repo,
check_run_id: runIDs.get(name),
check_run_id: check.id,
status: 'completed',
completed_at: new Date().toISOString(),
conclusion,
Expand Down
38 changes: 25 additions & 13 deletions scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

import { execSync } from 'node:child_process';
import { existsSync, mkdirSync, rmSync, globSync } from 'node:fs';
import { join } from 'node:path';
import { join, basename } from 'node:path';
import { parseArgs } from 'node:util';

const { values: options, positionals } = parseArgs({
options: {
// Output
help: { short: 'h', type: 'boolean', default: false },
verbose: { short: 'w', type: 'boolean', default: false },
quiet: { short: 'q', type: 'boolean', default: false },
'file-names': { short: 'N', type: 'boolean', default: false },
ci: { short: 'C', type: 'string' },

// Test behavior
test: { short: 't', type: 'string' },
force: { short: 'f', type: 'boolean', default: false },
auto: { short: 'a', type: 'boolean', default: false },
build: { short: 'b', type: 'boolean', default: false },
common: { short: 'c', type: 'boolean', default: false },
inspect: { short: 'I', type: 'boolean', default: false },
'exit-on-fail': { short: 'e', type: 'boolean' },
ci: { type: 'string' },

// Coverage
coverage: { type: 'string', default: 'tests/.coverage' },
preserve: { short: 'p', type: 'boolean' },
Expand All @@ -32,22 +37,26 @@ if (options.help) {
Paths: The setup files to run tests on
Options:
Behavior:
-a, --auto Automatically detect setup files
-b, --build Run the npm build script prior to running tests
-c, --common Also run tests not specific to any backend
-e, --exit-on-fail If any tests suites fail, exit immediately
-h, --help Outputs this help message
-w, --verbose Output verbose messages
-q, --quiet Don't output normal messages
-t, --test <glob> Which FS test suite(s) to run
-f, --force Whether to use --test-force-exit
-I, --inspect Use the inspector for debugging
--ci <check> Continuous integration (CI) mode. This interacts with the Github Checks API for better test status
Output:
-h, --help Outputs this help message
-w, --verbose Output verbose messages
-q, --quiet Don't output normal messages
-N, --file-names Use full file paths for tests from setup files instead of the base name
-C, --ci Continuous integration (CI) mode. This interacts with the Github
Checks API for better test status. Requires @octokit/action
Coverage:
--coverage <dir> Override the default coverage data directory
-p,--preserve Do not delete or report coverage data
-p, --preserve Do not delete or report coverage data
--report ONLY report coverage
--clean ONLY clean up coverage directory`);
process.exit();
Expand Down Expand Up @@ -115,7 +124,7 @@ function color(text, code) {
async function status(name) {
const start = performance.now();

if (options.ci) await ci.startCheck(options.ci);
if (options.ci) await ci.startCheck(name);

const time = () => {
let delta = Math.round(performance.now() - start),
Expand All @@ -132,11 +141,11 @@ async function status(name) {
return {
async pass() {
if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
if (options.ci) await ci.completeCheck(options.ci, 'success');
if (options.ci) await ci.completeCheck(name, 'success');
},
async fail() {
console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
if (options.ci) await ci.completeCheck(options.ci, 'failure');
if (options.ci) await ci.completeCheck(name, 'failure');
process.exitCode = 1;
if (options['exit-on-fail']) process.exit();
},
Expand Down Expand Up @@ -167,10 +176,13 @@ for (const setupFile of positionals) {
continue;
}

!options.quiet && console.log('Running tests:', setupFile);
process.env.SETUP = setupFile;

const { pass, fail } = await status(setupFile);
const name = options['file-names'] && !options.ci ? setupFile : basename(setupFile);

!options.quiet && console.log('Running tests:', name);

const { pass, fail } = await status(name);

try {
execSync(
Expand Down
File renamed without changes.

0 comments on commit ecda663

Please sign in to comment.