Skip to content

ci(bindings): run the Kotlin and Swift wire-contract tests on the shipped Binding Sets - #2937

Merged
zancas merged 10 commits into
devfrom
ci/binding-layer-crate-tests
Oct 15, 2026
Merged

zancas merged 10 commits into
devfrom
ci/binding-layer-crate-tests

Conversation

@zancas

@zancas zancas commented Oct 12, 2026

Copy link
Copy Markdown
Member

Step 1 of #2930. Each piece here adds coverage in zingolib. Nothing is deleted.

What was already true

zingo-ffi/lib is a root-workspace member, so test.yaml runs the wallet crate tests on every PR. The netutils job runs the proxy crate tests and the live mixnet start. The test a_mixnet_refusal_maps_to_the_owned_mixnet_marker has pinned the mixnet marker since a67de8d. Step 1 therefore reduces to the two wire-contract tests and the glossary.

The Kotlin wire-contract test

The Android binding build gains a test source set over contract-tests/kotlin, JUnit 4.13.2 as a test dependency, and the golden directory as the zingo.golden.dir system property. The package job of binding-layer-android.yaml runs testReleaseUnitTest after it bundles the AAR. The test then compiles against the Kotlin Binding Set that the AAR ships, so no host library is needed.

The Swift wire-contract test

The Swift test has never run anywhere. A test-only package at contract-tests/Package.swift depends on the ZingoBindings package by path, so the shipped Package.swift gains no test target. The test now imports that module. Its golden path was one directory short, and it gains the missing component. The iOS workflow runs the test under xcodebuild on an available simulator, because the XCFrameworks hold iOS slices only.

The glossary

zingolib/CONTEXT.md gains Binding Set, Binding Layer Endpoint and Bundle Pin. It restates Prebuilt Bundle to the packaged form that #2833 publishes. Ruling 9 of #2930 refers to these terms.

Verified

  • Both workflow files parse.
  • The Android binding build configures with the new wiring, and its unit-test classpath resolves JUnit and JNA.

Not verified here

  • The Kotlin test compiles only against generated bindings, which need the proxy crate's host build. The package job proves it.
  • The Swift step needs a Mac. The nightly and the publish workflows run it.

JUnit is a new Gradle test dependency. The test file imports it.

Refs #2930.

🤖 Generated with Claude Code

…pped Binding Sets

The proxy crate ships a Kotlin and a Swift wire-contract test beside its golden pins, and neither ran anywhere: zingo-mobile wired the Kotlin one into its own unit suite from the submodule, and the Swift one waited on #2504. Step 1 of #2930 moves both into zingolib, since each tests a Binding Set that zingolib publishes.

The Android binding build gains a test source set over the Kotlin test, JUnit 4 as a test dependency, and the golden directory as a system property. The package job of binding-layer-android.yaml runs it after bundling the AAR, against the Kotlin Binding Set the AAR ships. A test-only Swift package beside the Swift test depends on the ZingoBindings package by path, the test imports that module, and its golden path gains the directory it was missing. The iOS workflow runs it under xcodebuild on an available simulator, because the XCFrameworks hold iOS slices only.

The glossary gains Binding Set, Binding Layer Endpoint and Bundle Pin, and restates Prebuilt Bundle to the packaged form that #2833 publishes, so that the records of #2930 have a target for those terms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@juanky201271 juanky201271 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The Kotlin half holds: the test only uses allocationSize/write/read over a ByteBuffer, so it never touches UniffiLib and needs no host library. Two things on the Swift step, which the PR says no Mac has run, and one on docs.

Docs this PR leaves stale: contract-tests/README.md still says the app's Gradle unit suite runs the Kotlin test and that the Swift one joins the step-7 packaging (#2504); the headers of GoldenWireContractTest.kt ("zingo-mobile's test suite wires it in") and GoldenWireContractTests.swift say the same. zingolib runs both now; the README should name binding-layer-android.yaml and binding-layer-ios.yaml.

Comment thread .github/workflows/binding-layer-ios.yaml Outdated
Comment thread .github/workflows/binding-layer-ios.yaml Outdated
Comment thread zingolib/CONTEXT.md
zancas and others added 4 commits October 12, 2026 12:07
…contract test

The contract-tests package declares no products, so SwiftPM generates a
single scheme named after the package, ZingoBindingsContractTests-Package.
The step asked for GoldenWireContractTests, which is the test target, and
xcodebuild would have refused it on the first nightly run.

The simulator picker took the first device of the first runtime in the
JSON, so a watchOS or tvOS device could win, and an empty list ended in a
Python traceback. The step now filters with jq for an available iPhone on
an iOS runtime and fails with a device listing when none exists.

The tail that trimmed the xcodebuild log also hid the compile and link
errors above the trailer. The step passes -quiet instead, which keeps the
full set of errors, warnings and test results and drops the rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…S plan

The workbench now owns the contract-tests package as constants beside the
shipped package: its directory, its manifest, its package name, and the
relative path by which it reaches bindings/swift. A pin test reads the
manifest and asserts each constant against it, and resolves the relative
path back to the shipped package's directory, so a move of either package
fails in tools/workbench before it fails in a nightly.

The iOS plan of build-binding-layer ends with a new XcodeTest step. The
step first checks that the plan's output directory is the directory the
shipped manifest reads, so the test never compiles against stale
frameworks. It then lists the available simulators, picks the first iPhone
on an iOS runtime with a pure function that a fixture listing covers, and
runs the package's one scheme with -quiet. The iOS workflow calls the
builder alone, so a local run proves what CI proves.

The README and both test headers now say where each suite runs: the
Kotlin test in the Gradle library's unit suite, which the Android
workflow's package job runs, and the Swift test at the end of the iOS
plan. That package job runs one Gradle invocation for the AAR and the
test. The Gradle script derives the proxy crate from zingolibRoot instead
of a second relative path, and normalizes the golden directory. The
glossary's Binding Layer entry and the Android workflow's comments say
Binding Sets where they said bindings. A .gitignore beside the contract
manifest keeps SwiftPM's .build and .swiftpm out of the crate, and cargo
honours it when packaging.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
zancas and others added 2 commits October 14, 2026 08:20
…ong iOS output before the build, and require the executed test count

The simulator picker takes the iOS deployment target as its floor and
skips every runtime below it, prefers a booted iPhone, and returns the
device's udid beside its name. The xcodebuild destination names the
device by id, which is unambiguous across runtimes, where a bare name
is not.

The check that the iOS output directory is the one the Swift contract
test's package reads moves from the last step of the plan into build,
before any step runs, and compares the paths lexically. The old check
canonicalized a directory that does not exist in the case it guards
against, so it reported a resolve error in place of its own message,
and only after the whole iOS build. The step keeps the working
directory and the scheme alone, and the plan test no longer asserts a
mismatch under a name that claims agreement.

xcodebuild runs without -quiet, its output is captured and echoed, and
the builder parses the last "Executed N tests" line and requires N to
equal the suite's size, which a unit test pins to the number of test
methods in the Swift file. A scheme that discovers no test can no
longer pass.

The host working-directory check now guards captured commands as well
as streamed ones, through one helper, so an absent directory is named
before the program. The Swift test drops the ZINGO_GOLDEN_DIR override,
which xcodebuild never forwards to a simulator test host, and the
README states the absolute golden path the Gradle script sets. The
android workflow's header is rewrapped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@juanky201271 juanky201271 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All three addressed, thanks. The Kotlin step compiles and runs in the package job.

I dispatched ci-nightly on this branch and the Swift step fails at link time: ~37 undefined SystemConfiguration symbols from hickory_resolver / system_configuration in libzingo_nym_proxy_ffi.a (job). The shipped bindings/swift/Package.swift doesn't link the framework; zingo-mobile only works because its pbxproj links it by hand. linkerSettings: [.linkedFramework("SystemConfiguration")] on the ZingoBindings target should fix it. Good catch by the test, but as is this breaks the iOS job of the nightly and /publish.

nit: xcodebuild warned "Using the first of multiple matching destinations". The parser already has the UDID, so -destination "platform=iOS Simulator,id=<udid>" avoids that.

zancas and others added 2 commits October 14, 2026 15:32
The Swift wire-contract test fails at link time with undefined
SystemConfiguration symbols. The static library libzingo_nym_proxy_ffi.a
carries hickory_resolver's system_configuration crate, which calls into
that framework. The shipped bindings/swift/Package.swift linked no
framework, so every consumer had to link it by hand, as zingo-mobile's
pbxproj does.

The ZingoBindings target now declares the framework in its linker
settings. The workbench pin test asserts the setting through a named
constant, so the manifest cannot drop it unnoticed. This serves step 1 of
zingolib#2930.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@zancas

zancas commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

Thanks for dispatching the nightly. The link failure is real, and 87c401f does not fix it. 85b499d adds linkerSettings: [.linkedFramework("SystemConfiguration")] to the ZingoBindings target in bindings/swift/Package.swift and pins the setting in the workbench manifest test.

The destination nit is already in 87c401f. The nightly you dispatched ran at ac55e65. The step now passes -destination "platform=iOS Simulator,id=<udid>".

87c401f also closes eight items from a pass over ac55e65:

  1. Simulator choice. ios_simulator reads the runtime version of each simctl entry and skips runtimes below IOS_DEPLOYMENT_TARGET. It prefers a booted iPhone and otherwise takes the first eligible one. It returns the UDID.
  2. Output check. The built-versus-consumed check of --out compares the two paths lexically. It no longer canonicalizes the absent bindings/swift/build, so a wrong --out gets the authored message instead of "cannot resolve".
  3. Output check timing. That check now runs in build() for iOS, before any step. A wrong --out is refused before the thirty-minute build, not after it. The unit test asserts the refusal instead of a plan that must fail.
  4. GoldenWireContractTests.swift no longer reads ZINGO_GOLDEN_DIR. xcodebuild forwards only TEST_RUNNER_ variables, so the override never worked. The test resolves the golden directory from its own path.
  5. xcodebuild runs without -quiet. The step prints the log and parses the last "Executed N tests" line. It requires N to equal SWIFT_CONTRACT_TEST_COUNT, which is 3. A unit test pins that constant to the func test methods in the Swift file. Zero discovered tests now fail the step.
  6. The contract-tests README states that Gradle sets zingo.golden.dir to the absolute path of the golden directory.
  7. run_command and capture_command share one working-directory check. A missing contract-tests directory now names that directory instead of blaming xcrun.
  8. The header of binding-layer-android.yaml is rewrapped.

No Mac has run the Swift step since 87c401f. The iOS job on 85b499d is the proof.

…tination

xcodebuild still warned that it was using the first of multiple matching
destinations after the step named the simulator by udid. The arm64 and the
x86_64 entries of one simulator share that udid, so the udid alone matches
both. The destination now ends with arch=arm64, which matches one entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@zancas
zancas requested a review from juanky201271 October 14, 2026 23:45
@zancas
zancas dismissed juanky201271’s stale review October 14, 2026 23:56

Requesting a new review.

@zancas
zancas merged commit b5b2755 into dev Oct 15, 2026
42 checks passed
@zancas
zancas deleted the ci/binding-layer-crate-tests branch October 15, 2026 00:00
zancas added a commit that referenced this pull request Oct 16, 2026
fix(bindings): close the review findings that followed PR #2937
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