Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/scripts/artifact-workflow-filter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const { test } = require('node:test');

function conditionFor(file, job) {
const source = fs.readFileSync(path.join(__dirname, '../workflows', file), 'utf8').replace(/\r\n/g, '\n');
const jobBody = source.split(`\n ${job}:\n`)[1]?.split(/\n [\w-]+:\n/)[0];
const expression = jobBody?.match(/\n if: >\n((?: .*\n)+)/)?.[1].trim();
assert.ok(expression, `Missing condition for ${file}:${job}`);
// The conditions use only member access, == and &&, so they run as JavaScript.
// GitHub's == ignores case and a missing object yields null; JS models neither.
return new Function('github', `return Boolean(${expression});`);
}

function event(overrides = {}) {
return {
workflow: { path: '.github/workflows/ci.yml' },
workflow_run: { name: 'Build firmware', event: 'pull_request', conclusion: 'success' },
...overrides,
};
}

for (const [file, job] of [['pr-test-builds.yml', 'publish'], ['ci-size-report.yml', 'pr-comment']]) {
const accepts = conditionFor(file, job);
test(`${file}: accepts firmware without workflow_run.path`, () => {
assert.equal(accepts({ event: event() }), true);
});
test(`${file}: rejects same-name non-code workflow`, () => {
assert.equal(accepts({ event: event({ workflow: { path: '.github/workflows/non-code-change.yaml' } }) }), false);
});
test(`${file}: ignores a misleading run path`, () => {
const payload = event({ workflow: { path: '.github/workflows/non-code-change.yaml' } });
payload.workflow_run.path = '.github/workflows/ci.yml';
assert.equal(accepts({ event: payload }), false);
});
test(`${file}: rejects unsuccessful and non-PR runs`, () => {
for (const conclusion of ['failure', 'cancelled', 'skipped', 'action_required', null]) {
const payload = event();
payload.workflow_run.conclusion = conclusion;
assert.equal(accepts({ event: payload }), false);
}
const payload = event();
payload.workflow_run.event = 'push';
assert.equal(accepts({ event: payload }), false);
});
test(`${file}: rejects missing or unknown workflow paths`, () => {
for (const workflow of [{}, { path: '.github/workflows/other.yml' }]) {
assert.equal(accepts({ event: event({ workflow }) }), false);
}
});
}

test('firmware workflow path exists', () => {
assert.ok(fs.existsSync(path.join(__dirname, '../..', event().workflow.path)));
});
7 changes: 7 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ as `pr-test-builds.yml`** (secrets available even for fork PRs).
**Triggers:** Pull requests
**Purpose:** Detects PRs with only documentation/formatting changes

It shares the name `Build firmware` with `ci.yml` but produces none of its
artifacts. The `workflow_run` consumers `pr-test-builds.yml` and
`ci-size-report.yml` therefore also require
`github.event.workflow.path == '.github/workflows/ci.yml'` (a path, not a
workflow ID, so forks behave the same). Check both conditions with
`node --test .github/scripts/artifact-workflow-filter.test.js`.

## Configuration Files

- `../.github/stale.yml` - Stale issue/PR management
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci-size-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ name: CI Size Report
# of the two fires per PR and either satisfies the same required-status-
# check name. `workflow_run` can't tell the two apart by name (see
# github-actions-workflows.md), and the stub's only job is `test` — no
# `upload-artifacts`, none of the artifacts this job needs — so a guard
# step checks for that job directly before downloading anything.
# `upload-artifacts`, none of the artifacts this job needs — so pr-comment
# filters on the workflow path, and a guard step still checks that
# `upload-artifacts` succeeded before downloading anything.
#
# Requires the same repository secret PR_BUILDS_TOKEN as pr-test-builds.yml
# (Contents: write access to iNavFlight/pr-test-builds).
Expand Down Expand Up @@ -137,6 +138,7 @@ jobs:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.name == 'Build firmware' &&
github.event.workflow.path == '.github/workflows/ci.yml' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
concurrency:
Expand All @@ -154,12 +156,10 @@ jobs:
# untrusted code is never checked out in this privileged context.
- uses: actions/checkout@v4

# ci.yml's "Build firmware" name is shared with the non-code-change.yaml
# stub (see comment at the top of this file), whose only job is `test`
# — no upload-artifacts, none of the artifacts downloaded below. Check
# for that job directly rather than trusting the aggregate
# workflow_run.conclusion, which the job-level `if:` above already
# confirmed is 'success' for either workflow.
# The job-level `if:` already excludes the non-code-change.yaml stub by
# path (see comment at the top of this file). Still check
# upload-artifacts directly, so a renamed or skipped job skips the
# comment instead of failing on the downloads below.
- name: Check upload-artifacts job succeeded
id: check
env:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/pr-test-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ on:
jobs:
publish:
runs-on: ubuntu-latest
# Only act on pull_request-triggered runs that succeeded.
# Only act on successful pull_request runs of ci.yml. non-code-change.yaml
# shares the name "Build firmware" but uploads no artifacts.
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow.path == '.github/workflows/ci.yml' &&
github.event.workflow_run.conclusion == 'success'
# Prevent concurrent runs for the same PR branch racing on the
# release delete/create cycle.
Expand Down
Loading