Skip to content
Merged
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
17 changes: 17 additions & 0 deletions .github/actions/build-native/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Build the native tools
description: The CLAP prober, the LV2 measurer, their interposer and the fixture plugins the tests qualify.

# Shared by ci.yml and publish.yml. deps/ also holds omx-clap-core's LV2 feature provider at the packages' floor,
# built for the measurer to link; asan is the measurer under AddressSanitizer, which the leak check runs.
# src/workflows-agree.test.ts fails if a workflow stops using this action.

runs:
using: composite
steps:
- name: Build
shell: bash
run: |
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends liblilv-dev lv2-dev
make -C native deps all asan
23 changes: 23 additions & 0 deletions .github/actions/install-plugin-hostd/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Install plugin-hostd and its fake worker
description: plugin-hostd with pin_file (0.2.0 and after) and its fake worker, from the FreeMixer channel, for the pins-file test.

# Shared by ci.yml and publish.yml: both run the whole suite, and the pins-file test starts the real daemon on a
# qualified pins file. Bookworm is the suite built against the oldest glibc the channel serves, which the runner's
# Ubuntu runs; a floor only, no ceiling. src/workflows-agree.test.ts fails if a workflow stops using this action.

runs:
using: composite
steps:
- name: Install from the FreeMixer channel
shell: bash
run: |
set -euo pipefail
sudo install -d /etc/apt/keyrings
sudo curl -fsSL --retry 5 --retry-all-errors -o /etc/apt/keyrings/freemixer.asc https://freemixer.github.io/deb/freemixer.asc
echo "deb [signed-by=/etc/apt/keyrings/freemixer.asc] https://freemixer.github.io/deb/debian/bookworm ./" | sudo tee /etc/apt/sources.list.d/freemixer.list
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends plugin-hostd plugin-hostd-tests
# apt takes no version relation on the command line: the floor is checked on what it installed
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' plugin-hostd)" ge 0.2.0
echo "PLUGIN_HOSTD=/usr/bin/plugin-hostd" >> "$GITHUB_ENV"
echo "PLUGIN_HOSTD_FAKE_HOST=/usr/libexec/plugin-hostd/tests/fake-host" >> "$GITHUB_ENV"
27 changes: 3 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,9 @@ jobs:
- name: No tracked file points into a private repository
run: pnpm run check:private-refs

# the CLAP prober, the LV2 measurer, their interposer and the fixture plugins the tests qualify;
# deps/ also holds omx-clap-core's LV2 feature provider at the packages' floor, built for the measurer to link
# asan: the measurer under AddressSanitizer, which the leak check runs
- name: Build the native tools
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends liblilv-dev lv2-dev
make -C native deps all asan

# plugin-hostd with pin_file (0.2.0 and after) and its fake worker, from the FreeMixer channel: the pins-file
# test starts the real daemon on a qualified pins file. Bookworm is the suite built against the oldest glibc
# the channel serves, which the runner's Ubuntu runs; a floor only, no ceiling.
- name: Install plugin-hostd and its fake worker
run: |
set -euo pipefail
sudo install -d /etc/apt/keyrings
sudo curl -fsSL --retry 5 --retry-all-errors -o /etc/apt/keyrings/freemixer.asc https://freemixer.github.io/deb/freemixer.asc
echo "deb [signed-by=/etc/apt/keyrings/freemixer.asc] https://freemixer.github.io/deb/debian/bookworm ./" | sudo tee /etc/apt/sources.list.d/freemixer.list
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends plugin-hostd plugin-hostd-tests
# apt takes no version relation on the command line: the floor is checked on what it installed
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' plugin-hostd)" ge 0.2.0
echo "PLUGIN_HOSTD=/usr/bin/plugin-hostd" >> "$GITHUB_ENV"
echo "PLUGIN_HOSTD_FAKE_HOST=/usr/libexec/plugin-hostd/tests/fake-host" >> "$GITHUB_ENV"
- uses: ./.github/actions/build-native

- uses: ./.github/actions/install-plugin-hostd

- name: Type-check
run: pnpm lint
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,16 @@ jobs:
- name: Install pnpm
run: npm install -g pnpm@10.33.0

- name: Install
run: pnpm install --frozen-lockfile

# the same two steps ci.yml runs: the suite on a tag needs what the suite on a branch needs
- uses: ./.github/actions/build-native

- uses: ./.github/actions/install-plugin-hostd

- name: Build and test
run: |
pnpm install --frozen-lockfile
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends liblilv-dev lv2-dev
make -C native deps all asan
pnpm lint
pnpm test

Expand Down
62 changes: 62 additions & 0 deletions src/workflows-agree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/**
* ci.yml and publish.yml run the same suite, so they must prepare the runner the same way. The preparation lives
* once, as composite actions under `.github/actions/`; a workflow that copies the steps inline instead of using the
* action, or leaves them out, drifts from the other (publish.yml once lacked plugin-hostd and failed a tag at
* src/pins-file.test.ts, #84). This test reads both workflows as text and fails on that drift, and on an action
* that no longer sets the variables the suite asks for.
*/
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';

const read = (path: string): string => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');

const SHARED = ['build-native', 'install-plugin-hostd'] as const;
const WORKFLOWS = ['ci.yml', 'publish.yml'] as const;

/** The `uses:` lines of a workflow, in order, and the index of the step that runs the suite. */
function usesOf(workflow: string): string[] {
return [...read(`.github/workflows/${workflow}`).matchAll(/^\s*-?\s*uses:\s*(\S+)/gm)].map((m) => m[1]!);
}

function indexOfLine(text: string, needle: RegExp): number {
return text.split('\n').findIndex((line) => needle.test(line));
}

describe('ci.yml and publish.yml prepare the runner alike', () => {
for (const workflow of WORKFLOWS) {
const text = read(`.github/workflows/${workflow}`);
for (const action of SHARED) {
it(`${workflow} uses the shared ${action} action, once, before it lints`, () => {
const uses = usesOf(workflow).filter((u) => u === `./.github/actions/${action}`);
expect(uses).toHaveLength(1);
const at = indexOfLine(text, new RegExp(`uses: \\./\\.github/actions/${action}$`));
const lint = indexOfLine(text, /^\s*(run:\s*)?pnpm lint\s*$/);
expect(at).toBeGreaterThan(-1);
expect(lint).toBeGreaterThan(at);
});
}

it(`${workflow} carries no inline copy of the shared steps`, () => {
expect(text).not.toMatch(/apt-get install[^\n]*plugin-hostd/);
expect(text).not.toMatch(/make -C native deps/);
expect(text).not.toMatch(/PLUGIN_HOSTD/);
});
}

it('the install action sets what src/pins-file.test.ts asks for and checks the floor', () => {
const action = read('.github/actions/install-plugin-hostd/action.yml');
expect(action).toMatch(/>> "\$GITHUB_ENV"/);
for (const variable of ['PLUGIN_HOSTD', 'PLUGIN_HOSTD_FAKE_HOST']) {
expect(action).toContain(`echo "${variable}=`);
}
expect(action).toMatch(/dpkg --compare-versions .* ge 0\.2\.0/);
});

it('publish.yml publishes by OIDC: no token, no secret', () => {
const text = read('.github/workflows/publish.yml');
expect(text).toMatch(/id-token:\s*write/);
expect(text).not.toMatch(/NPM_TOKEN|NODE_AUTH_TOKEN|secrets\./);
});
});
Loading