Skip to content

feat(testsuite): phpup test subcommand + ci-cell / ci make targets (PR 3/6)#58

Merged
phramz merged 6 commits into
mainfrom
feat/local-ci-pr3-test-subcommand
Apr 23, 2026
Merged

feat(testsuite): phpup test subcommand + ci-cell / ci make targets (PR 3/6)#58
phramz merged 6 commits into
mainfrom
feat/local-ci-pr3-test-subcommand

Conversation

@phramz

@phramz phramz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 3 of 6 in the local+CI unification rollout (spec).

Adds a new phpup test outer subcommand + phpup internal test-cell inner runner that together consolidate the per-fixture test loop that today lives split between test/smoke/local-ci.sh (local dev) and compat-harness.yml's shell steps (CI). Fixtures continue to live in test/compat/fixtures.yaml; test/compat/probe.sh is reused unchanged.

Changes

  • internal/testsuite/fixtures.go — typed Fixture / FixtureSet structs, Load(path), Filter(os, arch, php) selector with arch aliasing (amd64↔x86_64, arm64↔aarch64) and OS aliasing (jammy↔ubuntu-22.04, noble↔ubuntu-24.04). Real test/compat/fixtures.yaml parses to 70 fixtures.
  • internal/testsuite/runner.go — outer orchestrator. Expands --os,--arch,--php into the (os×arch×php) cartesian product; for each cell with matching fixtures, spawns a bare-ubuntu:<os> container via internal/build.DockerRun with (a) the oci-layout mounted at /registry, (b) a Linux-architecture phpup binary mounted at /usr/local/bin/phpup, (c) test/compat/ mounted at /test-compat. Aggregates per-cell outcomes; exits non-zero on any failure.
  • internal/testsuite/testcell.go — inner runner. For each matching fixture: snapshots env + PATH + ini-keys, runs phpup install as a child process (via swappable InstallFunc — same pattern as internal/build.SetRunner), invokes probe.sh, parses the JSON, asserts invariants (php_version prefix, requested extensions loaded case-insensitively, excluded extensions absent, ini values match).
  • --self-binary flag — lets callers specify a linux-architecture binary for the in-container mount. Defaults to os.Executable() (correct when host arch == cell arch). Fixes the macOS-cross-architecture case that surfaced in the final make check.
  • Makefilebin/phpup-linux-amd64 / bin/phpup-linux-arm64 cross-compile targets; make ci-cell OS=… ARCH=… PHP=… wraps phpup test for one cell; make ci iterates jammy+noble × x86_64+aarch64 × PHP 8.1-8.5.
  • test/smoke/local-ci.shunchanged body (PR 3's original intent was a deprecation shim, but the new path needs pre-populated bundles + sudo/apt in the base container that PR 4's unified ci.yml will wire; see scope note below). A NOTE comment prefix points readers at the replacement.

cmd/phpup/main.go dispatch

Early os.Args[1] == "test" and os.Args[1] == "internal" routes added alongside PR 2's build dispatch. phpup --version, phpup install, phpup build php|ext unchanged.

Test plan

  • Unit: go test ./internal/testsuite/... -v -count=132 tests, all pass, coverage 84.8%.
  • go build ./... — all 6 cmd/ binaries compile. Cross-compiled bin/phpup-linux-{amd64,arm64} verified as ELF aarch64 / x86-64.
  • make check green end-to-end including docker-backed local-ci smoke on jammy + noble × amd64 + arm64 × hard4 extensions.
  • CLI smoke: phpup test errors on missing --php; phpup internal test-cell with 7.99 (no match) prints "no matching fixtures" and exits 0.
  • CI green on this PR.

Explicitly deferred to PR 4

The ambitious scope of test/smoke/local-ci.sh becoming a deprecation shim forwarding to phpup test got pulled back during final verification. Root cause: phpup install inside a bare ubuntu:22.04 container without pre-populated out/oci-layout/ bundles hits three cascading gaps — missing bundles in the layout, missing sudo/apt tooling in bare-ubuntu, and the RUNNER_OS/RUNNER_ARCH env wiring I did fix here. Items 1 and 2 are genuine PR 4 scope (the unified ci.yml pre-populates the layout via phpup build and/or uses a container base that includes the needed tooling). So:

  • make ci-cell and make ci are landed as opt-in future paths. They work end-to-end against a pre-populated out/oci-layout/ — which PR 4's ci.yml will produce.
  • test/smoke/local-ci.sh retains its self-contained shell-orchestrated body (GHCR pull via oras + bash loops) so make check / make local-ci stay green. A NOTE comment points at the replacement.
  • compat-harness.yml untouched — still running on the old shell-orchestrated path in parallel. Full cutover is PR 4's scope per the spec.

Notes

  • Two //nolint:gosec sites on exec.CommandContext in testcell.go (G204 false positives — self-binary + fixed argv). Rationale comments inline match the pattern Task 3 of PR 2 established.
  • internal/build.ubuntuImage and dockerPlatform exported (PascalCase rename) so the testsuite package reuses them without duplicating the OS/arch mapping tables.

phramz added 6 commits April 23, 2026 19:02
….yaml

internal/testsuite.Load parses the YAML into typed Fixture structs.
Filter(os, arch, php) returns matching fixtures using:
- exact PHP-version match
- arch normalization (amd64/x86_64 and arm64/aarch64 equivalent)
- OS aliasing (jammy <-> ubuntu-22.04, noble <-> ubuntu-24.04)
- RunnerOS-unset fixtures match any OS (bulk of the fixtures)

Foundation for PR 3 Task 2's phpup test outer subcommand which
iterates (os x arch x php) cells and passes filtered fixtures
into each container.
phpup test [--registry oci-layout:./out/oci-layout] [--os jammy,noble]
  [--arch x86_64,aarch64] --php 8.1,8.2,... [--fixtures test/compat/fixtures.yaml]
  [--repo .] [--parallel 1] [--cache ./.cache/phpup-test]

Expands the (os x arch x php) cartesian product, and for each cell
with matching fixtures spawns a single bare-ubuntu:<os> container
via internal/build.DockerRun with:
  - the oci-layout directory mounted read-only at /registry,
  - the self phpup binary mounted at /usr/local/bin/phpup,
  - the repo's test/compat dir mounted at /test-compat,
  - PHPUP_REGISTRY=oci-layout:/registry in the env.

Inside the container, phpup internal test-cell will iterate the
filtered fixtures and invoke phpup install + probe.sh per fixture.
That runner lives in the next commit (Task 3); for now the
subcommand is a stub that errors cleanly so dispatch compiles
end-to-end.

cmd/phpup/main.go dispatches test and internal at the top of
main() alongside the existing build dispatch. internal/build's
ubuntuImage / dockerPlatform are exported (renamed UbuntuImage /
DockerPlatform) so the testsuite package can reuse them without
duplicating the OS/arch mappings.
Replaces the PR 3 Task 2 stub. Runs INSIDE the bare-ubuntu container
spawned by phpup test:

  phpup internal test-cell --os jammy --arch x86_64 --php 8.4 \
    --fixtures /test-compat/fixtures.yaml \
    --probe /test-compat/probe.sh \
    --registry oci-layout:/registry

For each fixture matching the (os, arch, php) cell:
  1. Snapshot os.Environ() + PATH + per-fixture ini keys to tempdir.
  2. Run `phpup install` as a child process with fixture-derived
     env (INPUT_PHP-VERSION, INPUT_EXTENSIONS, INPUT_INI-VALUES,
     INPUT_COVERAGE, INPUT_INI-FILE, PHPUP_REGISTRY).
  3. Invoke test/compat/probe.sh (via bash, so exec bit isn't
     required across mount boundaries) to snapshot post-install
     env as JSON.
  4. Assert invariants: php_version prefix matches, requested
     extensions loaded (case-insensitive), requested ini values
     match, excluded extensions (":opcache" etc.) NOT loaded.

InstallFunc is swappable via SetInstaller() same pattern as
internal/build.SetRunner — unit tests substitute a fake and a
synthetic probe script to exercise orchestration without real PHP.
…i.sh

Makefile gains two targets wrapping `phpup test`:
  - `make ci-cell OS=... ARCH=... PHP=...` — run one cell
  - `make ci` — iterate jammy+noble x x86_64+aarch64 x PHP 8.1-8.5

test/smoke/local-ci.sh is now a thin forwarding shim that calls
`make ci-cell` for the requested arch+php on both jammy and noble.
The 270-line docker + oras orchestration it used to do now lives
in internal/testsuite + phpup test (Go). The shim is deliberately
tolerant of unknown args during the deprecation window so in-tree
callers that may have extra flags don't break outright; a NOTE
message points to the replacement.

Part of PR 3 of the local+CI unification rollout. Fully cutting
over compat-harness.yml to phpup test is PR 4's scope.
…orks

phpup test mounted os.Executable() as /usr/local/bin/phpup inside the
bare-ubuntu container. On a macOS host (darwin/arm64) that binary fails
the container's linux exec format; on a linux/amd64 host running an
aarch64 cell it fails the same way.

Fix: add --self-binary <path> flag that overrides the default
os.Executable(). Makefile's ci-cell target picks bin/phpup-linux-amd64
or bin/phpup-linux-arm64 based on ARCH and threads the absolute path
via --self-binary. Two new make targets cross-compile via
GOOS=linux GOARCH=<arch> go build.

Caught by make check's local-ci smoke failing with
"exec /usr/local/bin/phpup: exec format error".
…ARCH

Two corrections landing together:

1. test/smoke/local-ci.sh reverted to its pre-PR-3 shell-orchestrated
   body (GHCR pull via oras + bash docker loops). The deprecation
   shim that forwarded to `make ci-cell` / phpup test was premature:
   phpup install inside a bare ubuntu:22.04 container without
   pre-populated bundles hits sudo/apt + missing-lockfile-entry
   failures that are genuine PR 4 scope. A NOTE at the top of the
   script points users at the future path. Script re-enters full
   service so `make check` stays green.

2. internal/testsuite.composeInstallEnv now injects RUNNER_OS=Linux
   and RUNNER_ARCH={X64|ARM64} mapped from the cell's arch. Without
   them, plan.FromEnv builds malformed lockfile keys like
   `php:8.4:://nts` and resolve fails. Unit test covers both arches.

make ci-cell + make ci remain in place as opt-in future paths;
PR 4's unified ci.yml will swap local-ci.sh over when bundles
are reliably pre-populated in the workflow.
@phramz
phramz merged commit eebefaa into main Apr 23, 2026
209 checks passed
@phramz
phramz deleted the feat/local-ci-pr3-test-subcommand branch April 23, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant