Skip to content

Test native-image behavior in CI: nothing ever runs the built binary #133

Description

@rochala

Problem

#131 fixed a bug where no Scala 3 artifact had ever produced a docstring from a released native binary. The entire test suite passed throughout, and it would still pass if the bug were reintroduced.

Two distinct gaps let that happen.

1. No test asserts docstrings appear at all

grep -r docstring lib/test/src cli/test/src returns nothing. DocstringExtractor has no direct coverage and no integration-level assertion that a docstring reaches stdout.

This is cheap to close, and the fixtures are already set up for it — e.g. fixtureScala3/src/cellar/fixture/scala3/CellarTrait.scala carries /** Sealed ADT hierarchy for testing sealedChildren extraction. */, so an IntegrationTest case can fetch it from the local m2 repo and assert the text appears.

Note this alone would not have caught #131: the JVM path worked correctly. It closes a real coverage hole, not this bug class.

2. Nothing ever runs the native binary

This is the gap that matters. The failure mode of #131 was JVM works, native-image silently differs — invisible to ./mill _.test, which only ever runs on the JVM.

ci.yml runs ./mill _.test and nix flake show. cli.nativeImage is built only in snapshot.yml and release.yml, and in both cases the binary is packaged and published without being executed once. A binary that cannot resolve a stdlib, or cannot start at all, ships green.

Proposal

Add a native smoke job to ci.yml: build cli.nativeImage, then run the resulting binary and assert on real output. At minimum:

  • --version exits 0 and reports native-image as the runtime (CellarApp.scala:18 already distinguishes it)
  • get-external on a Scala 3 artifact prints a docstring — the Fix missing docstrings in the native-image binary #131 regression, and the one assertion that requires the coursier stdlib fetch to have resolved a real version
  • get-external on a Java artifact prints a signature — exercises the bundled-JRE path (bundledJreJar, build.mill:111), which is native-only by construction and equally untested

Scope and cost notes:

  • One target (linux-x86_64) is enough; the divergences are runtime/resource-embedding issues, not per-arch. No need to mirror snapshot.yml's 4-way matrix.
  • The native build takes ~90s locally warm, longer cold in CI once the GraalVM JVM download is counted. Put it in its own job with its own timeout-minutes rather than inside the existing test job, which is capped at 15.
  • Prefer the locally published fixtures (./mill publishFixtures) over a third-party coordinate for the docstring assertion, so CI does not depend on a Maven Central artifact staying put. The stdlib fetch still hits the network either way, which is the point.

Other native-only surfaces worth asserting once the harness exists

These share the #131 failure mode — no JVM test can see them:

  • resource embedding generally (this bug was compiler.properties not being embedded while library.properties was)
  • reflection/proxy config reachable only at image runtime
  • --java-home fallback and JRT filesystem behavior (graal#10013 is already worked around by hand)
  • slf4j-nop / logging init under native-image

Acceptance

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions