Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
6fcebaf
fix: a configuration group's lock keeps the other groups' entries (#903)
Sunrisepeak Nov 7, 2026
428070c
fix: a project's state directory ignores itself in a repository
Sunrisepeak Nov 7, 2026
afd9bff
fix: cache prune and gc --older-than collect an unused std module (#874)
Sunrisepeak Nov 7, 2026
9921564
feat: the low-space warning knows what the configuration's last build…
Sunrisepeak Nov 7, 2026
eb00081
feat: [build] target_budget keeps target/ within a size after every b…
Sunrisepeak Nov 7, 2026
8ef1409
build: mcpp's dependencies by identity, one table, one spelling of an…
Sunrisepeak Nov 7, 2026
57614ef
ci: a conditional directive or macro definition outside the system lo…
Sunrisepeak Nov 7, 2026
ed7aa6a
manifest: a quoted dotted dependency key is a deprecated reading that…
Sunrisepeak Nov 7, 2026
ee615d6
docs: target_budget, the low-space threshold, std collection and the …
Sunrisepeak Nov 7, 2026
4e14618
targets: both openkal rows are verified, an image run on Linux, macOS…
Sunrisepeak Nov 7, 2026
1ccfe66
ci: the modules wiring check reads mcpp's dependencies by identity ke…
Sunrisepeak Nov 7, 2026
3007e1f
test: 1028 checks for git itself; 'git' is no e2e capability
Sunrisepeak Nov 7, 2026
e26c3cd
fix: a workspace member's test finds the member's programs under --wo…
Sunrisepeak Nov 7, 2026
0a28de9
The built-in platform is openkal-platform 0.24.4
Sunrisepeak Nov 7, 2026
e8f5738
feat: mcpp test runs the runner's side of the test protocol
Sunrisepeak Nov 7, 2026
77364dc
feat: a mcpp::context states the engine's values to the build-program…
Sunrisepeak Nov 7, 2026
1ac7f6c
docs: cases and the test protocol, the record's protocol fields, test…
Sunrisepeak Nov 7, 2026
d0bc930
2026.11.8.1: version and CHANGELOG
Sunrisepeak Nov 7, 2026
1a7d971
feat: mcpp new tests with mtest and scaffolds a library; an entry off…
Sunrisepeak Nov 7, 2026
797398a
feat: mcpp test --guide names each test framework with its guide; a f…
Sunrisepeak Nov 7, 2026
1f812d5
build: mcpplibs.os 0.4.0; the launchers mcpp pack generates name thei…
Sunrisepeak Nov 7, 2026
c115a53
ci: the e2e timing tables measured again from the shards' reports; a …
Sunrisepeak Nov 7, 2026
d04bc47
ci: eight Linux e2e shards, about twelve minutes each by the measured…
Sunrisepeak Nov 7, 2026
af48763
test: a child that exits by itself is given a shell's start on a slow…
Sunrisepeak Nov 7, 2026
2269691
manifest: a quoted key that is the only spelling of a package beside …
Sunrisepeak Nov 7, 2026
34ff95b
fix: a test with a beginning links no main beneath its dev-dependency…
Sunrisepeak Nov 7, 2026
75891bd
fix: a test links a dev-dependency that is a shared library by its li…
Sunrisepeak Nov 7, 2026
669dc20
docs: the ecosystem's top-level design and the #885 supplement, with …
Sunrisepeak Nov 7, 2026
74ffa61
fix: a member's test begins by the member's own dev-dependencies, gat…
Sunrisepeak Nov 7, 2026
fcd48c8
build: mcpp dyndep leaves an unchanged output untouched, so a unit wh…
Sunrisepeak Nov 8, 2026
707ccc6
feat: a dev-dependency reaches the test units only; build and test sh…
Sunrisepeak Nov 8, 2026
71cd189
test: a fixture that replaces the skeleton's manifest leaves out its …
Sunrisepeak Nov 8, 2026
e2e64f7
docs: the record of the dev-dependency boundary, the test plan's prun…
Sunrisepeak Nov 8, 2026
b8638c3
refactor: the usage requirements the edges carry are defined beside t…
Sunrisepeak Nov 8, 2026
f8c173d
docs: W8, the tests on mtest and the CI's time, measured and open; th…
Sunrisepeak Nov 8, 2026
782d59c
docs: W8 confirmed, after this round's releases, with its probe first
Sunrisepeak Nov 8, 2026
26024c8
fix: a marked program that writes no program event is one case; a fai…
Sunrisepeak Nov 8, 2026
5d4cecf
feat: [test] programs = "directory"; mcpp test --since and --changed …
Sunrisepeak Nov 8, 2026
30abbc0
test: the e2e suite as mtest programs: run_all.sh runs it by mtest wh…
Sunrisepeak Nov 8, 2026
8073523
docs: W8 is this round's, its mtest part as implemented
Sunrisepeak Nov 8, 2026
317c870
fix: trim_paths applies to the code under test; a test program's own …
Sunrisepeak Nov 8, 2026
2adce9c
ci: the e2e shards run by mtest two at a time, divided by main's hist…
Sunrisepeak Nov 8, 2026
07dc961
docs: the e2e suite by mtest
Sunrisepeak Nov 8, 2026
51301de
docs: the W8 probe's measurements; test: 955 keeps self env's answer …
Sunrisepeak Nov 8, 2026
549b269
docs: the unit-test measurement of the W8 probe
Sunrisepeak Nov 8, 2026
3d6723e
scaffold: the skeleton's tests use mtest 0.2.0, the release one const…
Sunrisepeak Nov 8, 2026
24172c0
docs: W9, a case's kind as its suite's type, attributes by scope, and…
Sunrisepeak Nov 8, 2026
d8410c6
docs: W9 as implemented: an object is a case, and the code states how…
Sunrisepeak Nov 8, 2026
059d97b
test: the skeleton's tests in mtest 0.3.0's form; --case reaches the …
Sunrisepeak Nov 8, 2026
154fac0
docs: W9's hosts, editors and mcppls as implemented
Sunrisepeak Nov 8, 2026
c576628
fix: a source whose main no command compiles is no beginning of a pro…
Sunrisepeak Nov 8, 2026
a3588ca
ci: the e2e suite runs under mtest 0.3.0
Sunrisepeak Nov 8, 2026
d977c4a
docs: the mtest examples name 0.3.0
Sunrisepeak Nov 8, 2026
262d801
docs: W10, mtest's paradigm and the test tree of mcppls
Sunrisepeak Nov 8, 2026
8445a9f
docs: W10's unit tests are a program per file
Sunrisepeak Nov 8, 2026
d88391c
test: a program mcpp test starts gets only the protocol variables it …
Sunrisepeak Nov 8, 2026
e3c02f0
mtest 0.4.0: the e2e engine, the scaffold's and the guide's release
Sunrisepeak Nov 8, 2026
2cbf131
fix: a framework that is a dependency and a dev-dependency offers its…
Sunrisepeak Nov 8, 2026
9a8dba2
docs: W10 as implemented: the harness, the topics, the measurements a…
Sunrisepeak Nov 8, 2026
7e83178
test: 231 and 234 take mcpp at a Windows path as it is
Sunrisepeak Nov 8, 2026
48003ab
test: a Windows run's report names its tests without the CR Python's …
Sunrisepeak Nov 8, 2026
94cdd0f
fix: two builds that find a std module missing compile it once
Sunrisepeak Nov 8, 2026
0edbb8f
docs: U6 decided
Sunrisepeak Nov 8, 2026
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
242 changes: 242 additions & 0 deletions .agents/docs/2026-11-08-ecosystem-885-supplement-design.md

Large diffs are not rendered by default.

448 changes: 448 additions & 0 deletions .agents/docs/2026-11-08-ecosystem-top-level-design.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

353 records.
355 records.

## By subject

Expand Down Expand Up @@ -75,6 +75,8 @@ Records that declare one. Everything else is listed by date below.

### ecosystem

- [生态顶层方案:#885 收尾之后的工作流、原则与依赖](2026-11-08-ecosystem-top-level-design.md) — active
- [#885 收尾补充方案:目标角色、平台事实、系统接口与版本书写](2026-11-08-ecosystem-885-supplement-design.md) — active
- [生态 #885 收尾设计:核心、插件、openkal、语言服务与编辑器的一轮闭合](2026-11-07-ecosystem-885-closeout-design.md) — active
- [生态设计 part 2:openkal 的最小原子与二进制映像、其上的 `mcpplibs.os`,以及 part 1 的收尾与回归记录](2026-10-26-ecosystem-part2-os-library-over-openkal-design.md) — active
- [控制契约、并发与性能、单一 C++ 运行时,以及 IDE 生态(生态级,#838、#839)](2026-10-26-control-contract-runtime-and-ide-ecosystem-design.md) — active
Expand Down Expand Up @@ -177,6 +179,8 @@ Records that declare one. Everything else is listed by date below.

### 2026-11

- [生态顶层方案:#885 收尾之后的工作流、原则与依赖](2026-11-08-ecosystem-top-level-design.md) — active
- [#885 收尾补充方案:目标角色、平台事实、系统接口与版本书写](2026-11-08-ecosystem-885-supplement-design.md) — active
- [生态 #885 收尾设计:核心、插件、openkal、语言服务与编辑器的一轮闭合](2026-11-07-ecosystem-885-closeout-design.md) — active
- [Launch configuration and named test runners](2026-11-06-launch-configuration-and-named-test-runners.md) — active
- [Compiler attempt lifetime and output recovery](2026-11-06-compiler-attempt-recovery.md) — active
Expand Down
92 changes: 92 additions & 0 deletions .github/actions/e2e-mtest/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: e2e-mtest
description: >
mtest for the e2e suite (design 2026-11-08 W8; tests/e2e/mtest.toml): the
release this action pins, its sha256 checked, and the stores main's last
successful CI run kept for this host, taken into tests/e2e/.mtest so that
`--shard` divides the suite by measured time and a run orders its tests
longest first. Exports E2E_MTEST for run_all.sh. A run with no store of main
divides by count, and its own store becomes the next run's history.

inputs:
host:
description: the host's name in the stores' artifact names (linux, macos, windows)
required: true
version:
description: the mtest release
required: false
default: '0.4.0'
sha256:
description: sha256 of mtest-<version>-openkal-bundle-all.tar.gz
required: false
default: 'b62e4ac7cd76b20dbdc48a5d041d377444e8815ee7a4ff1e7686145d13acc925'
token:
description: a token that reads this repository's actions
required: false
default: ${{ github.token }}

runs:
using: composite
steps:
- name: mtest
shell: bash
env:
MTEST_VERSION: ${{ inputs.version }}
MTEST_SHA256: ${{ inputs.sha256 }}
run: |
set -euo pipefail
archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz"
temp="$RUNNER_TEMP"; python=python3
if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi
curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \
"https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"
actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive")
[ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, the action pins $MTEST_SHA256"; exit 1; }
mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest"
launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest"
[ "$RUNNER_OS" = Windows ] && launcher="$launcher.exe"
"$launcher" --version
echo "E2E_MTEST=$launcher" >> "$GITHUB_ENV"
# The pull request's change, for the `pr` set: the base's tip and HEAD's
# parents, so that `git diff <base>...HEAD` has its merge base. When they
# cannot be fetched, mtest selects every test and says why.
- name: The base of the change
if: github.base_ref != ''
shell: bash
run: |
git fetch --no-tags --depth=1 origin "+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}" || true
git fetch --no-tags --deepen=1 origin || true
- name: The stores of main's last CI run
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
HOST: ${{ inputs.host }}
run: |
set -euo pipefail
run=$(gh run list -R "$GITHUB_REPOSITORY" --workflow ci.yml --branch main --status success --limit 1 \
--json databaseId -q '.[0].databaseId' 2>/dev/null || true)
if [ -z "$run" ]; then echo "no successful run of main: the suite is divided by count"; exit 0; fi
rm -rf "$RUNNER_TEMP/e2e-stores"
if ! gh run download "$run" -R "$GITHUB_REPOSITORY" -p "e2e-store-$HOST-*" -D "$RUNNER_TEMP/e2e-stores" 2>/dev/null; then
echo "main's run $run kept no store for $HOST: the suite is divided by count"; exit 0
fi
for store in "$RUNNER_TEMP"/e2e-stores/*/; do
(cd tests/e2e && "$E2E_MTEST" store import --from "$store")
done
echo "history of main's run $run: $(ls "$RUNNER_TEMP/e2e-stores" | wc -l) store(s)"
# The coverage map of the last nightly measurement (e2e-coverage.yml), on
# the host it was measured on: the sources each test's run executed there.
# Another host's own sources (src/native/windows/...) run on no Linux test,
# so its suite has no map and every test of it is affected.
- name: The coverage map
if: inputs.host == 'linux'
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
run: |
set -euo pipefail
run=$(gh run list -R "$GITHUB_REPOSITORY" --workflow e2e-coverage.yml --status success --limit 1 \
--json databaseId -q '.[0].databaseId' 2>/dev/null || true)
if [ -z "$run" ]; then echo "no coverage map: every test is affected"; exit 0; fi
rm -rf "$RUNNER_TEMP/e2e-coverage"
gh run download "$run" -R "$GITHUB_REPOSITORY" -n e2e-coverage-linux -D "$RUNNER_TEMP/e2e-coverage" || exit 0
(cd tests/e2e && "$E2E_MTEST" coverage import --from "$RUNNER_TEMP/e2e-coverage/coverage.json")
38 changes: 36 additions & 2 deletions .github/tools/check_e2e_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@
An exception for a test that ran, or for a test that does not exist, also fails
the check, so that the list cannot outlive the reason it records.

Some claims need a test to pass on particular hosts, not on some host: docs/21
calls a target row `verified` when CI builds and runs its artefact, and the
`*-openkal` rows claim that for every system. tests/e2e/host-requirements.tsv
lists such a test with the hosts its result must come from; a listed test that
did not pass on one of them, or that does not exist, fails the check.

With --timings-out DIR it writes `<host>.tsv`, the measured duration of every
test that ran, merged across that host's shards, in the format run_all.sh reads
from E2E_TIMINGS; refreshing tests/e2e/timings/ is copying those files. A report
from E2E_TIMINGS (a run without mtest; CI's shards divide by mtest's history). A report
file is named `e2e-report-<host>-<shard>.tsv`.

Usage:
Expand Down Expand Up @@ -64,6 +70,19 @@ def read_reports(directory: Path):
yield m["host"], int(m["shard"]), parts[0], parts[1], ms, parts[3] if len(parts) > 3 else ""


def read_host_requirements(path: Path) -> dict[str, set[str]]:
"""`<test>\t<host> <host> ...\t<reason>` lines: the hosts a test must pass on."""
out: dict[str, set[str]] = {}
if not path.exists():
return out
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip() or line.startswith("#"):
continue
parts = line.split("\t")
out[parts[0].strip()] = set(parts[1].split()) if len(parts) > 1 else set()
return out


def read_exceptions(path: Path) -> dict[str, str]:
out: dict[str, str] = {}
if not path.exists():
Expand Down Expand Up @@ -109,8 +128,10 @@ def main() -> int:
workflows = workflow_tokens([p.read_text(encoding="utf-8")
for p in sorted((root / ".github" / "workflows").glob("*.yml"))])
exceptions = read_exceptions(root / "tests" / "e2e" / "coverage-exceptions.tsv")
required = read_host_requirements(root / "tests" / "e2e" / "host-requirements.tsv")

ran: dict[str, set[str]] = defaultdict(set)
passed: dict[str, set[str]] = defaultdict(set)
skipped: dict[str, set[str]] = defaultdict(set)
shard_ms: dict[tuple[str, int], int] = defaultdict(int)
timings: dict[str, dict[str, int]] = defaultdict(dict)
Expand All @@ -119,6 +140,8 @@ def main() -> int:
reports += 1
if status in RAN:
ran[test].add(host)
if status == "pass":
passed[test].add(host)
shard_ms[(host, shard)] += ms
timings[host][test] = ms
elif status == "skip":
Expand All @@ -143,6 +166,15 @@ def main() -> int:
for test in exceptions:
if test not in tests:
stale.append(f"{test} is excused but does not exist")
unmet = []
for test, hosts in sorted(required.items()):
if test not in tests:
stale.append(f"{test} is required on hosts but does not exist")
continue
for host in sorted(hosts - passed[test]):
why = [d for d in skipped[test] if d.startswith(f"{host}: ")]
unmet.append(f"{test} did not pass on {host}"
f"{' (' + '; '.join(why) + ')' if why else (' (it ran and did not pass)' if host in ran[test] else ' (no report of it there)')}")

print(f"{len(tests)} tests: {counts['ran']} ran on a shard, {counts['job']} run by a "
f"dedicated job, {counts['excused']} excused, {len(uncovered)} uncovered")
Expand All @@ -152,14 +184,16 @@ def main() -> int:
print(f"UNCOVERED: {line}")
for line in stale:
print(f"STALE EXCEPTION: {line}")
for line in unmet:
print(f"REQUIRED ON HOST: {line}")

if args.timings_out:
out = Path(args.timings_out)
out.mkdir(parents=True, exist_ok=True)
for host, table in timings.items():
(out / f"{host}.tsv").write_text(
"".join(f"{t}\t{ms}\n" for t, ms in sorted(table.items())), encoding="utf-8")
return 1 if uncovered or stale else 0
return 1 if uncovered or stale or unmet else 0


if __name__ == "__main__":
Expand Down
21 changes: 15 additions & 6 deletions .github/tools/check_include_regions.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# check_include_regions.sh — headers are included only where a system has no
# module for what is needed (design 2026-11-07 D10).
# check_include_regions.sh — headers are included, and the preprocessor
# chooses, only where a system has no module for what is needed (design
# 2026-11-07 D10; design 2026-11-08 S4).
#
# mcpp imports the standard library (`import std`), its dependencies as modules
# (nlohmann.json, mcpplibs.os, the mwrap wrappers of zlib, zstd, xz and of the
Expand All @@ -10,6 +11,11 @@
# directive anywhere else is a header a module should provide: depend on the
# package that wraps it, or wrap it (mcpp-index docs/mwrap.md).
#
# The same holds for a conditional directive (`#if`, `#ifdef`, `#ifndef`,
# `#elif`) and a macro definition (`#define`): a platform is chosen by
# `if constexpr` over mcpplibs.os and by the per-system sources of mcpp.toml,
# never by the preprocessor.
#
# Text that only looks like a directive is not one: a raw string (the source
# mcpp writes for a launcher or a template) and a comment are skipped. Tests
# (`tests/`, a module's `tests/`) are outside the rule.
Expand All @@ -20,10 +26,11 @@ import re, sys
from pathlib import Path

ALLOWED = {Path("src/native/posix/lock.cpp"), Path("src/native/windows/lock.cpp")}
DIRECTIVE = re.compile(r'#\s*(include|if|ifdef|ifndef|elif|elifdef|elifndef|define)\b')
RAW = re.compile(r'(?:u8|u|U|L)?R"([^()\\ ]{0,16})\(')

def directives(text):
"""Line numbers of `#include` directives outside comments and raw strings."""
"""(line, directive) of each preprocessor directive outside comments and raw strings."""
out, i, n, line = [], 0, len(text), 1
at_line_start = True
while i < n:
Expand All @@ -45,7 +52,8 @@ def directives(text):
j += 2 if text[j] == '\\' else 1
i = j + 1; at_line_start = False; continue
if at_line_start and c == '#':
if re.match(r'#\s*include\b', text[i:]): out.append(line)
d = DIRECTIVE.match(text, i)
if d: out.append((line, d.group(1)))
if not c.isspace(): at_line_start = False
i += 1
return out
Expand All @@ -54,8 +62,9 @@ bad = 0
for path in sorted(list(Path("src").rglob("*.cpp*")) + list(Path("modules").rglob("*.cpp*"))):
if "tests" in path.parts or "test" in path.parts or path in ALLOWED:
continue
for line in directives(path.read_text(encoding="utf-8", errors="replace")):
print(f"{path}:{line}: #include outside the system lock files; import a module instead")
for line, d in directives(path.read_text(encoding="utf-8", errors="replace")):
hint = "import a module instead" if d == "include" else "choose with if constexpr or a per-system source"
print(f"{path}:{line}: #{d} outside the system lock files; {hint}")
bad += 1
for path in ALLOWED:
if not path.exists():
Expand Down
8 changes: 5 additions & 3 deletions .github/tools/check_modules_wiring.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# when they are forgotten:
#
# 1. modules/<x>/mcpp.toml the package itself
# 2. mcpp.toml [dependencies.mcpp] what makes it importable
# 2. mcpp.toml [dependencies] `mcpp.<x> = { path = "modules/<x>" }`,
# what makes it importable
# 3. mcpp.toml [workspace] members what makes `-p` and `mcpp test` see it
# 4. the two xmake source lists the bootstrap and the benchmark, which
# have no package manager and compile the
Expand Down Expand Up @@ -62,8 +63,9 @@ for d in "${dirs[@]}"; do
|| bad "$d/mcpp.toml has namespace = \"${ns:-<none>}\"; modules of this project use \"mcpp\" so they do not squat bare registry names"

# (2) importable
grep -qE "^[[:space:]]*$name[[:space:]]*=[[:space:]]*\{[[:space:]]*path[[:space:]]*=[[:space:]]*\"modules/$name\"" mcpp.toml \
|| bad "mcpp.toml [dependencies.mcpp] has no entry for '$name' — it will not be importable"
grep -qE "^[[:space:]]*mcpp\.$name[[:space:]]*=[[:space:]]*\{[[:space:]]*path[[:space:]]*=[[:space:]]*\"modules/$name\"" \
<(sed -n '/^\[dependencies\]/,/^\[/p' mcpp.toml) \
|| bad "mcpp.toml [dependencies] has no 'mcpp.$name = { path = \"modules/$name\" }' — it will not be importable"

# (3) addressable
grep -qF "\"modules/$name\"" <(sed -n '/^\[workspace\]/,/^\[/p' mcpp.toml) \
Expand Down
37 changes: 29 additions & 8 deletions .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: ci-linux-e2e

# The e2e suite (tests/e2e/run_all.sh) on Linux, called by ci.yml beside
# ci-linux.yml after the Linux build. Six shards, assigned by measured
# duration (tests/e2e/timings/linux.tsv, rule R4 of the 2026-10-02 CI record):
# the table holds about 72 minutes of tests (2026-10-30, with the target-set
# and packaging tests of 2026.10.30.1), about 12 a shard,
# ci-linux.yml after the Linux build. Eight shards, divided by mtest by the
# measured history of main's stores (design 2026-11-08 W8; rule R4 of the
# 2026-10-02 CI record): about 94 minutes of tests (2026-11-08), about 12 a
# shard one test at a time and about 6 two at a time,
# each running the one binary build.yml produced (use-built-mcpp, rule R1).
# Each shard writes a per-test report that the e2e-coverage job of ci.yml reads
# (rule R5).
Expand All @@ -17,16 +17,21 @@ on:

jobs:
e2e:
name: e2e ${{ matrix.shard }}/6 (linux x86_64, self-host)
name: e2e ${{ matrix.shard }}/8 (linux x86_64, self-host)
runs-on: ubuntu-24.04
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
shard: [1, 2, 3, 4, 5, 6, 7, 8]
env:
E2E_SHARD: ${{ matrix.shard }}/6
E2E_TIMINGS: tests/e2e/timings/linux.tsv
E2E_SHARD: ${{ matrix.shard }}/8
# By mtest (design 2026-11-08 W8; .github/actions/e2e-mtest): parts by
# the measured history of main's stores, two tests at a time (measured on
# one Linux shard: 220 s against 424 s one at a time, three no faster),
# and on a pull request the tests its change affects.
E2E_JOBS: '2'
E2E_SET: ${{ github.event_name == 'pull_request' && 'pr' || '' }}
E2E_REPORT: ${{ github.workspace }}/e2e-report-linux-${{ matrix.shard }}.tsv
MCPP_HOME: /home/runner/.mcpp
# NOTE: do NOT force MCPP_VERBOSE here. The e2e suite includes tests that
Expand Down Expand Up @@ -79,6 +84,10 @@ jobs:
XLINGS_HOME="$MCPP_HOME/registry" "$MCPP_VENDORED_XLINGS" install \
mcpplibs:[email protected] mcpplibs:[email protected] -y

- uses: ./.github/actions/e2e-mtest
with:
host: linux

- name: E2E suite
# About twice the shard's budget of ten minutes (R4): reached only by a
# hang. The per-test 600 s limit in run_all.sh names the test that hung.
Expand All @@ -98,6 +107,18 @@ jobs:
"$MCPP" self config
bash tests/e2e/run_all.sh

# The shard's store: main's are the history the next runs divide the suite by.
- name: Upload the shard's store
if: always()
timeout-minutes: 5
uses: actions/upload-artifact@v4
with:
name: e2e-store-linux-${{ matrix.shard }}
path: tests/e2e/.mtest
include-hidden-files: true
if-no-files-found: ignore
retention-days: 14

# One file, no glob: the report the e2e-coverage job of ci.yml reads.
- name: Upload the shard's report
if: always()
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,20 @@ jobs:
with:
host: linux-x86_64

# A pull request runs the tests its change affects (`mcpp test --since`,
# docs/08; design 2026-11-08 W8): the base's tip is fetched, and when it
# cannot be, every test runs.
- name: The base of the change
if: github.base_ref != ''
shell: bash
run: |
if git fetch --no-tags --depth=1 origin "+refs/heads/$GITHUB_BASE_REF:refs/remotes/origin/$GITHUB_BASE_REF"; then
echo "MCPP_TEST_SINCE=origin/$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi

- name: Unit + integration tests via `mcpp test`
run: |
"$MCPP_FRESH" test
"$MCPP_FRESH" test ${MCPP_TEST_SINCE:+--since "$MCPP_TEST_SINCE"}

# Each package under `modules/` carries its own tests, and they are built
# as that package ALONE -- a configuration the root suite never produces,
Expand Down Expand Up @@ -81,7 +92,7 @@ jobs:
echo "members: $(echo $members | tr '\n' ' ')"
for m in $members; do
echo "::group::mcpp test -p $m"
"$MCPP_FRESH" test -p "$m"
"$MCPP_FRESH" test -p "$m" ${MCPP_TEST_SINCE:+--since "$MCPP_TEST_SINCE"}
echo "::endgroup::"
done

Expand Down
Loading
Loading