Skip to content

test: installed-binary tests with manifest-gated publish - #5

Merged
trygu merged 10 commits into
masterfrom
feat/install-execution-tests
Oct 17, 2026
Merged

trygu merged 10 commits into
masterfrom
feat/install-execution-tests

Conversation

@trygu

@trygu trygu commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Adds realistic installed-binary (install + execute) release tests to the release pipeline.

What changes

  • Build -> upload -> independent install tests -> publish. Every build job now writes a per-artifact manifest (filename, SHA-256, product version/commit, pinned engine SHA, HiGHS version) into its artifact.
  • Three fresh-environment install jobs (linux x86_64 in an ubuntu:22.04 container, macos arm64, windows x86_64) that never check out the product: they install the exact binary of this run into an empty library in a spaced path, verify its identity against the build manifest (version, engine SHA, HiGHS, bundled native lib layout, loaded-library location), and run the full test suite.
  • New ex1 test (tests/testthat/test_installed_arb.R): the complete portable ex1 .arb job through the public run_arb() driver: OPT suppression, type-1 matrix output and the native type-5 AR+ protection audit, validated against hand-derived expectations shared with the Python installed tests (exact cells and values, the preserved manual primary with adequate protection, every withheld cell strictly interior; the secondary pattern itself is not pinned).
  • Linux floor job keeps the clean-room checks (no build paths, no host C++ runtime, static source-tarball checks); its inline smoke is replaced by the full installed suite.
  • Publish gates on tag: the publish job verifies every file against the build manifests, asserts platform coverage and lockstep, and publishes exactly the verified files plus an aggregated release-manifest.json.
  • PRs run the build matrix plus all install tests; publishing stays tag-gated.

Verified locally: staged build of the package, fresh spaced-path library, tools/install-check.R green, full suite 409 passed / 0 failed / 20 skipped, plus a negative check (a corrupted expectation fails the protection assertions).

Build -> upload -> independent install tests -> publish:

- each build job writes a per-artifact manifest (filename, sha256,
  product version, product commit, engine sha, HiGHS version)
- test-resource job ships the test suite and tools scripts as the
  r-install-tests artifact; install jobs never check out the product
- three fresh-environment install jobs (linux x86_64 in an
  ubuntu:22.04 container keeping the clean-room checks, macos arm64,
  windows x86_64) install the exact binary of this run into an empty
  library in a spaced path, verify its identity against the build
  manifest, and run the full test suite, which now includes the
  portable ex1 .arb job (OPT suppression plus type-1 output and the
  native type-5 AR+ protection audit, validated against
  hand-derived expectations shared with the Python installed tests)
- the publish job verifies every file against the build manifests,
  asserts platform coverage and lockstep, and publishes exactly the
  verified files plus an aggregated release-manifest.json
- PRs get the build matrix plus all install tests; publishing stays
  tag-gated
- replaces the inline clean-room smoke (subsumed by the full install
  test); the ldd/NEEDED and source-tarball checks stay in the floor
  job

Verified locally: staged build, fresh spaced-path library,
install-check green, full suite 409 passed 0 failed
Copilot AI balanced review requested due to automatic review settings October 17, 2026 09:53
The CRAN test dependencies are installed by install.packages, which
needs a writable library; on the macos and windows runners the
stripped default user library would not be one. The freshness
guarantee is the empty per-job library (RLIB) first in the path, not
a stripped environment.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Manifest generation and installation-test defects block the release pipeline, while unexpected audit statuses can bypass validation.

7 open issues
What changed in this PR

Adds installed-binary validation to the R release pipeline, with publishing gated on installation tests and artifact manifests.

Changes:

  • Adds fresh-environment installation tests for Linux, macOS, and Windows.
  • Adds an ex1 suppression and protection-audit test with portable fixtures.
  • Adds artifact manifests and pre-publish verification.
File Description
tools/​install-check.R Checks installed package identity and native-library layout.
tests/​testthat/​test_installed_arb.R Validates suppression, protection audit, and published output.
tests/​testthat/​fixtures/​ex1/​sto.cdl Defines size-class codes.
tests/​testthat/​fixtures/​ex1/​reg.cdl Defines region codes.
tests/​testthat/​fixtures/​ex1/​ex1.tab Supplies input cells and manual suppression status.
tests/​testthat/​fixtures/​ex1/​ex1.rda Defines table metadata.
tests/​testthat/​fixtures/​ex1/​ex1.expected Records expected values and protection requirements.
tests/​testthat/​fixtures/​ex1/​ex1.arb Supplies the example batch.
.github/​workflows/​release.yml Adds manifests, installation jobs, and publishing gates.

Review effort: Balanced

echo " \"highs_version\": \"$HIGHS\","
echo " \"platform\": \"${{ matrix.osname }}-${{ matrix.arch }}\","
echo " \"files\": ["
echo " {\"name\": \"$BIN\", \"sha256\": \"$(sha_of "build/bin/$BIN")\", \"size\": $(wc -c < "build/bin/$BIN" | tr -d ' ')}]"
echo " \"highs_version\": \"$HIGHS\","
echo " \"platform\": \"source\","
echo " \"files\": ["
echo " {\"name\": \"$SRC\", \"sha256\": \"$(sha_of "build/dist/$SRC")\", \"size\": $(wc -c < "build/dist/$SRC" | tr -d ' ')}]"
Comment thread .github/workflows/release.yml Outdated
} > "build/dist/manifest-source.json"
fi
cat build/bin/manifest-*.json
[ -f build/dist/manifest-source.json ] && cat build/dist/manifest-source.json
name: assemble install test resource
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
expect_identical(as.numeric(r[14L]), 0)
expect_identical(as.numeric(r[15L]), 0)
} else {
skip(paste("unexpected status", st, "for", key))
Comment thread tools/install-check.R Outdated

stopifnot(dir.exists(file.path(lib, "rtauargus.native")))
pkg_path <- find.package("rtauargus.native", lib.loc = lib)
stopifnot(normalizePath(pkg_path) == file.path(lib, "rtauargus.native"))
Comment thread tools/install-check.R Outdated
if (length(main) != 1L)
stop("the rtauargus.native shared object is not loaded")
cat("loaded native library:", main, "\n")
if (!startsWith(main, normalizePath(pkg_path)))
trygu added 8 commits October 17, 2026 13:40
The manifest step ended with `[ -f ... ] && cat ...`; on macos and
windows (no source tarball) the test is the last command and exits 1
under pipefail. Use an if/fi block.
On Windows the getLoadedDLLs path and normalizePath may disagree on
the separator style, so startsWith could fail on a correct install.
Normalize both sides; a no-op on POSIX.
…rence

GNU tar on the Windows runner cannot write zips, so
`tar -a -c -f x.zip` silently emitted a plain tar with a .zip
name; R CMD INSTALL cannot unzip it (the 0.5.6 Windows binary
shipped in exactly this state). Create the zip with PowerShell
Compress-Archive and verify it with R's own zip reader
(tools/check-zip.R). Also allow the main module's self-reference
in the installed dylib check: it is linked against its own
install name, as in the 0.5.6 binary.
…lve libR path

R's unpackPkgZip derives the top-level directory it looks for
from the zip file name (basename minus .zip and the trailing
_[0-9.-]+), so a zip containing only rtauargus.native/ failed with
'cannot open ... DESCRIPTION'. Stage the installed package under
the derived name before zipping, and teach tools/check-zip.R the
expected directory. Also fix the macOS dylib reference check: the
module references libR.dylib by its versioned framework path, but
R RHOME returns the versionless R.framework/Resources symlink, so
resolve through R.home() + normalizePath.
…d package

Two root causes from run 39035454677:

- windows install: R's unpackPkgZip derives both the top-level
  directory it looks for in the zip and the library directory it
  installs into from the zip file name (basename minus .zip and the
  trailing _[0-9.-]+), ignoring the DESCRIPTION Package field. The
  platform-suffixed name installed the package under
  rtauargus.native_1.3.7_r4.5.3_windows-x86/, so the install check
  could not find rtauargus.native/. Name the zip in the CRAN form
  rtauargus.native_<version>.zip (the platform stays in the
  manifest); the derived name is then exactly the package name.

- macos install: the test-resource artifact carried the un-staged
  engine tests, which call tauargusengine:: explicitly, but the
  staged binary carries the binding functions in the
  rtauargus.native namespace (stage-r-package.sh rewrites the same
  tests in the build checkout). testthat 3.3.2 auto-skips missing
  :: packages only when on_cran(), so the bare linux container
  (NOT_CRAN unset) hid the mismatch with 19 skips while the macos
  and windows runners (NOT_CRAN=true) failed. The test-resource job
  now applies the same rewrite, with a guard that fails if stale
  references survive.
R CMD INSTALL of the correctly named zip now installs the package
where expected, but the strict normalizePath equality failed on the
windows install job. find.package is already restricted to the
install library (a real mis-installation fails at the dir.exists
check just above), so a case-only difference between normalizePath's
disk-case answer and the constructed path is cosmetic on
case-insensitive file systems: warn with both values instead of
failing, and keep a hard failure with diagnostics for any real
mismatch.
The round-7 diagnostic showed the mismatch was a separator style,
not a case difference:

  found:    C:\Users\runneradmin\rta install\Rlib\rtauargus.native
  expected: C:\Users\runneradmin\rta install\Rlib/rtauargus.native

On Windows R defines FILESEP as '/' (src/include/Defn.h), so
file.path() joins with forward slashes while normalizePath()
outputs backslashes by default (winslash='\\'). The check
compared the two raw outputs, which can never match on Windows.
Normalize both sides; the directory identity is then compared in
one canonical form on every platform.
The install URL in the release-notes template still pointed at
the platform-suffixed zip name. R's zip installer derives the
package directory from the file name, so the released asset is
named rtauargus.native_<version>.zip. Note the broken 0.5.6
Windows asset (a plain tarball mislabeled as a .zip).
@trygu
trygu merged commit 069f5f4 into master Oct 17, 2026
16 checks passed
@trygu
trygu deleted the feat/install-execution-tests branch October 17, 2026 17:03
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.

2 participants