Merge pull request #95 from storytold/fix/nextest-0.9.150

ci: pin nextest to 0.9.150
This commit is contained in:
skylar taylor-barrick
2026-10-26 16:37:11 -06:00
committed by GitHub
4 changed files with 169 additions and 6 deletions
+2 -2
View File
@@ -47,10 +47,10 @@ on:
required: false
default: "wasm32-unknown-unknown"
tools:
description: "Prebuilt cargo tools from taiki-e/install-action, comma-separated, each with its version (e.g. cargo-nextest@0.9.149,cargo-deny@0.20.2); empty installs none"
description: "Prebuilt cargo tools from taiki-e/install-action, comma-separated, each with its version (e.g. cargo-nextest@0.9.150,cargo-deny@0.20.2); empty installs none"
type: string
required: false
default: "cargo-nextest@0.9.149"
default: "cargo-nextest@0.9.150"
env:
description: "KEY=VALUE lines added to the environment of every leg before the cache is restored"
type: string
+2 -2
View File
@@ -121,7 +121,7 @@ jobs:
rustup default stable
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29
with:
tool: cargo-llvm-cov,cargo-nextest
tool: cargo-llvm-cov,cargo-nextest@0.9.150
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -186,7 +186,7 @@ jobs:
libgl1-mesa-dev mesa-vulkan-drivers
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29
with:
tool: cargo-nextest
tool: cargo-nextest@0.9.150
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: nextest-archive
+17 -2
View File
@@ -340,7 +340,7 @@ The quality gate for an app's pull requests and pushes to main. It runs `command
persisted credentials, and craft-i18n when the app has `craft-i18n.rev`;
- `packages` installed through `actions/install-apt-packages` on Linux legs;
- the channel from `rust-toolchain.toml`, or `toolchain`, with clippy, rustfmt and `targets`;
- prebuilt cargo tools from `tools` (by default `cargo-nextest`);
- prebuilt cargo tools from `tools` (by default `cargo-nextest@0.9.150`);
- `CARGO_PROFILE_DEV_DEBUG=line-tables-only` (set `dev-debug: ""` to keep the repository's
profile) and the caller's `env`, `linux-env` and `windows-env` lines, each `KEY=VALUE`;
- a rust-cache dependency cache that only the default branch saves, so pull requests restore
@@ -382,6 +382,21 @@ jobs:
Concurrency belongs to the caller: a reusable workflow cannot set it for the run.
The gate default and sharded SonarCloud coverage pin cargo-nextest to **0.9.150**, which fixes
[GHSA-vc25-qg58-w57c](https://github.com/nextest-rs/nextest/security/advisories/GHSA-vc25-qg58-w57c).
A caller overriding `tools` must also update its own nextest pin; changing the shared default
cannot override it. Set a required minimum at the top of the caller's `.config/nextest.toml`,
keeping its existing profiles and test groups:
```toml
nextest-version = "0.9.150"
```
A `recommended` minimum only warns; it does not enforce this requirement. The minimum-version
check does not upgrade a developer's installed executable, and can be explicitly overridden.
Do not enable experimental run recording or rerun untrusted portable recordings with an
affected version. Ordinary compiled test archives used by coverage are a different feature.
An app that also checks Windows on main, on manual runs and on pull requests labelled `full-ci`,
with per-OS environment and a shorter Windows gate (PDFCraft's call):
@@ -395,7 +410,7 @@ jobs:
packages: >-
libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev
libgtk-3-dev qpdf poppler-utils
tools: cargo-nextest@0.9.149,cargo-deny@0.20.2
tools: cargo-nextest@0.9.150,cargo-deny@0.20.2
linux-env: PDFCRAFT_ORACLES_REQUIRED=1
windows-env: PDFCRAFT_REQUIRE_WINRES=1
# Windows checks what differs by platform: the build (with resources) and the tests.
+148
View File
@@ -0,0 +1,148 @@
"""Keep shared nextest installers on the reviewed fix for GHSA-vc25-qg58-w57c.
Only YAML data is inspected. Negative controls mutate actual parsed installer values in memory;
no workflow scripts, nextest executable, test binary or recording are executed.
"""
from copy import deepcopy
from pathlib import Path
import re
import unittest
import yaml
ROOT = Path(__file__).resolve().parents[1]
PIN = 'cargo-nextest@0.9.150'
# install-action installs this one tool under two names, `cargo-nextest` and the documented alias
# `nextest`; matching the crate name alone would count an alias installer as nothing at all.
NEXTEST_NAMES = ('cargo-nextest', 'nextest')
GATE = '.github/workflows/gate.yml'
SONAR = '.github/workflows/sonarcloud.yml'
def workflow_documents():
documents = {}
for directory in (ROOT / '.github/workflows', ROOT / 'actions'):
for path in sorted(directory.rglob('*')):
if path.is_file() and path.suffix in ('.yml', '.yaml'):
documents[path.relative_to(ROOT).as_posix()] = yaml.load(
path.read_text(), Loader=yaml.BaseLoader)
return documents
def tool_values(value, path=()):
"""Find literal installer tool lists and reusable workflow tools defaults."""
if isinstance(value, dict):
for key, child in value.items():
child_path = path + (key,)
if key in ('tool', 'tools') and isinstance(child, str):
yield child_path, child
elif key == 'tools' and isinstance(child, dict) and 'default' in child:
yield child_path + ('default',), child['default']
yield from tool_values(child, child_path)
elif isinstance(value, list):
for index, child in enumerate(value):
yield from tool_values(child, path + (index,))
def is_nextest(spec):
"""Whether a taiki-e/install-action spec installs nextest, under either of its two names."""
return spec.split('@', 1)[0] in NEXTEST_NAMES
def nextest_tool_values(documents):
for filename, document in documents.items():
for path, value in tool_values(document):
if isinstance(value, str):
specs = re.split(r'[,\s]+', value.strip())
if any(is_nextest(spec) for spec in specs):
yield filename, path, value, specs
def replace_value(document, path, value):
for key in path[:-1]:
document = document[key]
document[path[-1]] = value
class NextestVersionTests(unittest.TestCase):
def setUp(self):
self.documents = workflow_documents()
def assert_fixed_installers(self, documents):
# Discover every tool/default site, not just fixtures for the three known sites.
found = list(nextest_tool_values(documents))
self.assertEqual(len(found), 3, 'expected gate default and both coverage installers')
self.assertEqual(sorted(filename for filename, _, _, _ in found), [GATE, SONAR, SONAR])
for filename, path, _, specs in found:
# Either name installs the tool, so either name has to reach this assertion.
nextest = [spec for spec in specs if is_nextest(spec)]
# Do not wrap this assertion in subTest: negative controls must catch the exception.
self.assertEqual(nextest, [PIN],
f'{filename}:{path}: nextest must use the exact reviewed fixed release')
def test_every_shared_nextest_installer_is_fixed(self):
self.assert_fixed_installers(self.documents)
def test_discovers_the_documented_alias_at_every_site(self):
# Spelling every installer with the alias must still inventory the same three sites, so an
# alias installer is examined rather than skipped.
changed = deepcopy(self.documents)
for filename, path, value, _ in nextest_tool_values(self.documents):
replace_value(changed[filename], path, value.replace('cargo-nextest', 'nextest'))
found = list(nextest_tool_values(changed))
self.assertEqual(sorted((filename, value) for filename, _, value, _ in found),
sorted([(GATE, 'nextest@0.9.150'),
(SONAR, 'cargo-llvm-cov,nextest@0.9.150'),
(SONAR, 'nextest@0.9.150')]))
def test_gate_default_still_reaches_the_installer_and_allows_caller_overrides(self):
gate = self.documents[GATE]
self.assertEqual(gate['on']['workflow_call']['inputs']['tools']['default'], PIN)
installers = [step for step in gate['jobs']['gate']['steps']
if step.get('uses', '').startswith('taiki-e/install-action@')]
self.assertEqual(len(installers), 1)
self.assertEqual(installers[0]['with']['tool'], '${{ inputs.tools }}')
self.assertEqual(installers[0]['if'], "inputs.tools != ''")
def test_coverage_keeps_llvm_cov_and_the_two_nextest_installers(self):
jobs = self.documents[SONAR]['jobs']
for job, expected in [('build', 'cargo-llvm-cov,' + PIN), ('coverage', PIN)]:
with self.subTest(job=job):
installers = [step for step in jobs[job]['steps']
if step.get('uses', '').startswith('taiki-e/install-action@')]
self.assertEqual(len(installers), 1)
self.assertEqual(installers[0]['with']['tool'], expected)
def test_rejects_vulnerable_floating_prerelease_and_duplicate_pins_at_each_real_site(self):
# The duplicates keep one fixed selector, so the site is still discovered and still
# counted as one site: only the validation filter can reject the vulnerable selector
# beside it, which is what pins the alias there independently of discovery.
bad_pins = ('cargo-nextest@0.9.149', 'cargo-nextest',
'cargo-nextest@latest', 'cargo-nextest@0.9.150-rc.1',
'nextest@0.9.149', 'nextest',
'nextest@latest', 'nextest@0.9.150-rc.1',
PIN + ',cargo-nextest@0.9.149', PIN + ',nextest@0.9.149')
for filename, path, value, _ in nextest_tool_values(self.documents):
for bad in bad_pins:
with self.subTest(filename=filename, path=path, bad=bad):
changed = deepcopy(self.documents)
replace_value(changed[filename], path, value.replace(PIN, bad))
with self.assertRaises(AssertionError):
self.assert_fixed_installers(changed)
def test_rejects_an_additional_vulnerable_installer(self):
# One control per name: the alias installs the same tool, so an extra alias installer must
# be counted instead of passing the inventory unnoticed.
for tool in ('cargo-nextest@0.9.149', 'nextest@0.9.149'):
with self.subTest(tool=tool):
changed = deepcopy(self.documents)
changed[SONAR]['jobs']['coverage']['steps'].append({
'uses': 'taiki-e/install-action@' + '0' * 40,
'with': {'tool': tool},
})
with self.assertRaises(AssertionError):
self.assert_fixed_installers(changed)
if __name__ == '__main__':
unittest.main()