Skip to content

AB#12334: Prototype APK-compatible test lanes on Helix - #12479

Draft
mmitche wants to merge 40 commits into
mainfrom
prototype/host-build-tests-helix
Draft

mmitche wants to merge 40 commits into
mainfrom
prototype/host-build-tests-helix

Conversation

@mmitche

@mmitche mmitche commented Aug 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Guarded APK-lane Helix prototype for AB#12334. The proven host-test prototype remains preserved at prototype/host-build-tests-helix-preserved (188932a).

The public pipeline opt-in is enableApkTestsHelixPrototype=true; the final default is false. apkTestsHelixTargetMinutes defaults to 15. Existing macOS APK jobs remain enabled unless apkTestsHelixOnly=true is selected for prototype-only diagnosis.

Architecture

A Windows Azure Pipelines job builds and stages the eight measured APK flavors once: Debug, Release, NoAab, CoreCLR, Mono, CoreCLRTrimmable, NativeAOT, and JcwGen. Each deterministic work item contains one signed APK plus metadata and scripts; the API 29 Ubuntu Android queue supplies the x64 emulator and adb, so platform-tools are not duplicated.

The build preserves the normal generated application manifest, injects only instrumentation and local-network permissions, validates the packaged binary manifest with aapt, and retains per-flavor binlogs/manifests. The Linux runner waits for the x64 emulator and package service, bounds every adb operation, installs the APK, runs am instrument, captures console/logcat/device/package state, pulls TRX, and propagates failures. Process-global JNI count tests tracked by #12031 run in a fresh process; their TRX is merged with the remaining inventory and published centrally.

At the 15-minute target, each flavor is already an indivisible batch of hundreds of tests below the target, so the prototype emits eight flavor work items rather than one test per item.

Exact same-commit parity

Run: dnceng-public build 1575692 at source head 5dbbb63.

Helix correlation: 5dccff50-899e-4a07-b4b3-a4ef3d5116a6.

Both existing macOS APK jobs and all eight Helix work items succeeded. Comparison used fully-qualified test identity plus outcome multisets:

Flavor Existing Helix Inventory differences Outcome differences
Debug 1,055: 992 pass / 63 skip same 0 0
Release 368: 310 / 58 same 0 0
NoAab 368: 310 / 58 same 0 0
CoreCLR* 368: 310 / 58 same 0 0
Mono 305: 260 / 45 same 0 0
CoreCLRTrimmable 703: 691 / 12 same 0 0
NativeAOT 670: 659 / 11 same 0 0
JcwGen 39: 39 / 0 same 0 0

* The explicit CoreCLR lane was recently removed as a duplicate; its Helix result was compared with Release, whose exact inventory/outcomes are equivalent.

Helix TRX publication produced eight Azure test runs successfully. NativeAOT's isolated JNI check failed its first process-global count attempt and passed the retained retry, consistent with #12031.

Timing and payload evidence

Final correlation had zero initial queue delay, completed in 8.94 minutes, and used one available emulator worker serially. Work-item durations:

  • CoreCLRTrimmable 1m15s
  • CoreCLR 1m14s
  • Debug 1m25s
  • JcwGen 31s
  • Mono 1m08s
  • NativeAOT 35s
  • NoAab 1m21s
  • Release 1m17s

Compressed work-item payloads were 14.8-86.2 MiB, 267.5 MiB total. The only shared correlation payload was a 2.3 KiB Helix command bundle; no SDK/workload/platform-tools payload is repeated on Helix.

End-to-end preparation remains the main limitation: the Helix AzDO job was 42.3m versus 40.5m/32.9m for the two existing macOS APK jobs. The device correlation itself is fast, but building eight packages sequentially offsets that gain. Queue capacity also varied in earlier experiments (0-33m queue wait).

Jonathan's lane-split concern

The experiment confirms the proposed split is safest:

  • Debug must embed assemblies for direct APK installation, so the existing lane must retain fast-deployment/sideload coverage.
  • Existing AAB configurations are converted to APK for this prototype, so the existing lane must retain Play-style bundle/split installation coverage.
  • NoAab and JcwGen are the lowest-risk first production Helix candidates; the all-eight guarded run was retained to prove inventory/outcome behavior and quantify the boundary.

Validation

  • build-tools/automation/apk-tests-helix/ApkTestsHelix.Tests.ps1 passes (Windows/Linux generation, bash/PowerShell syntax, manifest preservation, deterministic metadata).
  • Changed MSBuild XML parses; git diff --check passes.
  • Build 1573542 first proved all eight Helix work items; build 1575692 proved exact same-commit parity and successful TRX publication.
  • Final guarded head e63e99d passed build 1575806; the APK prototype stage was absent at its default-off setting.

Tracking

@mmitche mmitche changed the title Prototype duration-balanced host build tests on Helix AB#12334: Prototype duration-balanced host build tests on Helix Aug 21, 2026
@jonathanpeppers

jonathanpeppers commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

@mmitche I wanted to look into this, but I think the "APK Test" lanes might be suitable for Helix to prototype first.

The NUnit (ancient!) MSBuild integration tests we have, would require installing .NET SDK w/ the currently built Android workload on Helix -- and I'm not sure if Helix can do that or not. Those NUnit tests create projects, build them, run them on eumulators, and assert the output.

@simonrozsival enabled a simple Android arm32 job here, so Helix is being used & working in one place so far:

@mmitche

mmitche commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

@mmitche I wanted to look into this, but I think the "APK Test" lanes might be suitable for Helix to prototype first.

The NUnit (ancient!) MSBuild integration tests we have, would require installing .NET SDK w/ the currently built Android workload on Helix -- and I'm not sure if Helix can do that or not. Those NUnit tests create projects, build them, run them on eumulators, and assert the output.

@simonrozsival enabled a simple Android arm32 job here, so Helix is being used & working in one place so far:

Good to know, thanks.

Build the existing Android device-test flavors in Azure Pipelines, submit one deterministic work item per flavor to the proven Android Helix queue, and centrally publish pulled TRX and device diagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmitche
mmitche force-pushed the prototype/host-build-tests-helix branch from 188932a to 350fab9 Compare August 26, 2026 21:19
@mmitche mmitche changed the title AB#12334: Prototype duration-balanced host build tests on Helix AB#12334: Prototype APK test lanes on Helix Aug 26, 2026
mmitche and others added 21 commits August 26, 2026 15:12
Resolve the prepared SDK once, pass SDK/JDK paths to every clean/build invocation, and create the diagnostics root before any build can fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the proven MAUI R2R Helix payload layout by shipping platform-tools beside each APK instead of relying on correlation payload extraction layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the Windows public build agent so each work item receives a compatible adb.exe, while starting after the existing macOS artifact build only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Include the USERPROFILE android-toolchain location used by the Windows setup template.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shut down dotnet build servers after staging each flavor so shared TestRunner.Core outputs are not locked by the next APK build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the pipeline variable established before APK builds when staging Windows platform-tools for Helix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reset LASTEXITCODE after accepted robocopy success codes so APK submission preparation does not fail after generating all work items.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the same explicit artifact paths as the existing APK lanes instead of selecting a potentially stale recursive match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the same dotnet test/MTP build path as the existing APK lanes, tolerate the expected no-device launch failure on the preparation agent, and require the instrumented APK output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use aapt badging output to stage the exact instrumentation component from each built APK and include installed instrumentation in device diagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove downloaded package artifacts before SignAndroidPackage, then require and inspect the newly built APK so instrumentation diagnostics cannot be based on stale outputs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow the optional label and other aapt badging fields between instrumentation name and target package, and print badging on future parse failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build each Helix APK with a minimal explicit instrumentation entry and the actual application package, then verify the resulting component with aapt before submission.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use project-relative obj/Helix manifest paths because the Android build treats AndroidManifest as project-relative even when given an absolute path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use a conditional MSBuild import so only the APK test project receives the generated manifest while project references retain their normal manifests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply the app-only AndroidManifest override through CustomAfterMicrosoftCommonTargets and log the resolved value before Android package validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Let the two device-test projects consume a private Helix-only AndroidManifest path directly, avoiding global property propagation to project references.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Temporarily enable the guarded stage by default on this draft branch so the public PR pipeline can provide a same-commit dual-path run while Azure CLI authentication is unavailable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the AndroidManifestOverlay item intended for additive manifest entries so the normal generated test manifest and explicit instrumentation are both packaged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Inject a Helix-only assembly-level Instrumentation attribute into each test application so normal manifest generation emits the runner component without replacing the app manifest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add explicit assembly-level Instrumentation attributes to both device-test applications so normal SignAndroidPackage builds retain their runners without pipeline manifest overrides.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmitche
mmitche force-pushed the prototype/host-build-tests-helix branch from cce79ef to 188932a Compare August 28, 2026 13:25
@mmitche mmitche changed the title AB#12334: Prototype APK test lanes on Helix AB#12334: Prototype duration-balanced host build tests on Helix Aug 28, 2026
@mmitche

mmitche commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Thanks — I paused the host-only iteration and built the APK-lane prototype to test this suggestion.

The APK lanes are definitely critical-path relevant (they ended the longest path in 3/13 P80 builds), and their eight device invocations are already natural sub-15-minute work items. The Android Helix payload itself is also much smaller.

The blocker I found is the current MTP artifact boundary:

  • SignAndroidPackage emits an APK without the instrumentation entry used by dotnet test; verified with aapt dump badging in build 1571272.
  • dotnet test selects a device before producing the runnable test package, so it cannot currently act as a build-only step on the preparation agent (build 1570967).
  • Five existing flavors are AAB-based, so exact reuse also needs the installed split-APK set or bundletool/signing plumbing.
  • One Android Helix work item was assigned a queue machine with no connected device in build 1569756.

I preserved that experiment on prototype/apk-tests-helix, but restored this PR to the host prototype rather than mixing both designs.

The original SDK/workload concern is now demonstrated as solved: build 1568901 ran the old and Helix host paths from the same commit and succeeded with exact parity — Windows 1,504/1,504 and Linux 167/167, with identical pass/skip outcomes. Helix reduced the Windows execution tail from ~100m to ~44m and Linux BuildTest from 74m to ~21m after submission. The tradeoff is still-large shared payloads (9.04 GiB Windows / 6.11 GiB Linux), now documented in the PR.

My recommendation is to keep the proven host path here, then make “export the exact runnable MTP package/split set” a prerequisite for a follow-up APK Helix migration.

Temporarily default this draft branch to the APK-only validation path so public CI can verify the build-server shutdown fix from build 1569600 without unrelated test stages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmitche
mmitche force-pushed the prototype/host-build-tests-helix branch from 188932a to 9fe8d92 Compare August 28, 2026 13:30
@mmitche mmitche changed the title AB#12334: Prototype duration-balanced host build tests on Helix AB#12334: Prototype APK test lanes on Helix Aug 28, 2026
@jonathanpeppers

Copy link
Copy Markdown
Member

@mmitche ok, I see probably two issues:

  • Debug mode APKs, we "sideload" all the .dll files. This would require our deploy step (MSBuild logic) to run on Helix, which I think it basically just installs .apk files. This is real test coverage; we don't want something to break in debug mode.
  • .aab files don't work on Helix? This is also real test coverage: simulating what apps installed from Google Play do.

So, I think an initial path forward could be;

  • APK Test Lane 1 -- all the combinations that don't work on Helix, basically as-is
  • APK Test Lane 2 -- run all the other combinations on Helix.

mmitche and others added 17 commits August 28, 2026 07:36
Use a project-local opt-in property so only the test application consumes the generated instrumentation manifest. Preserve build binlogs and merged manifest diagnostics for failed payload preparation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
aapt badging omits the instrumentation record even when the packaged binary manifest contains it. Inspect AndroidManifest.xml directly and retain both aapt views for diagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Inject instrumentation into the normal generated manifest, embed Debug assemblies for direct installation, grant Android 16/17 local-network permissions, retain device diagnostics, and retry unhealthy device assignments once.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wait for delayed Android device attachment, retain per-attempt TRX output, retry a failed instrumentation run once, and keep first-cause diagnostics from being masked during cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PowerShell 5.1 promoted adb daemon startup stderr into a terminating error before device discovery. Capture native stderr without masking the process exit code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the public Ubuntu Android 29 emulator queue that matches the existing APK lanes, generate a bash work-item runner, and use queue-provided adb instead of Windows platform-tools.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Normalize generated bash scripts before Helix upload so the Ubuntu Android queue can execute them without CRLF parsing failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve adb from Android SDK environment variables and known queue paths, with a bounded filesystem fallback when the queue does not add platform-tools to PATH.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wait for an online API 29 emulator, select its serial explicitly, and scope every adb command so the queue's second offline emulator cannot make commands ambiguous.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Select the queue's x86_64 API 29 emulator when both x86 and x86_64 devices are online so the staged APK native libraries match the device ABI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wait for the API 29 package service, retry transient installs, add exact test-name instrumentation filters, and run the JNI count checks tracked by #12031 in fresh processes before merging their TRX results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-enable the existing test stages alongside the now-passing Helix prototype so the macOS APK lanes and all eight Helix flavors execute from the same commit at the 15-minute target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register the TRX namespace as the default before merging isolated and remaining results so Azure Pipelines parses and publishes every Helix test run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the ElementTree merge command at Python top level while registering the default TRX namespace, avoiding the indentation failure seen in parity run 1575040.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Do not fall back to the API 29 x86 emulator while x86_64 is still booting; the staged APKs contain x64 native libraries and cannot install on the 32-bit device.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply explicit time limits to boot, package-service, install, instrumentation, pull, and cleanup commands so an unresponsive API 29 emulator fails causally instead of consuming the entire Helix work-item timeout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Return the public pipeline to opt-in behavior after the exact same-commit parity run and remove an unrelated formatting-only change from the prototype history.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmitche mmitche changed the title AB#12334: Prototype APK test lanes on Helix AB#12334: Prototype APK-compatible test lanes on Helix Sep 1, 2026
@mmitche

mmitche commented Sep 1, 2026 •

Copy link
Copy Markdown
Member Author

@jonathanpeppers I ran the all-eight prototype far enough to answer the lane-split question with same-commit evidence. Build 1575692 ran both existing macOS APK jobs and Helix correlation 5dccff50-899e-4a07-b4b3-a4ef3d5116a6: all eight work items passed, with zero fully-qualified inventory or pass/fail/skip differences for every flavor. TRX publication also succeeded.

The operational evidence supports your proposed split: Debug had to embed assemblies, so Helix does not replace fast-deployment/sideload coverage; AAB flavors were converted to APK, so Helix does not replace Play-style split coverage. NoAab and JcwGen are the safest first production candidates. The correlation itself took 8.94m with 267.5 MiB total payload, but sequential package preparation made the prototype job 42.3m, so package production/reuse is the next optimization before moving a lane. The PR body has the full parity and timing table.

@jonathanpeppers

Copy link
Copy Markdown
Member

I think we could split this up as:

  • Lane 1, current setup: Debug w/ fast dev, a single Release-mode aab run
  • Lane 2, helix: All other combinations

We might currently have multiple aab files, but we could reduce down to a single one on Lane 1 with Release mode w/ default configuration.

I'd be OK taking out of draft and merging, if it's working in this way.

@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12479

@github-actions github-actions Bot 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.

⚠️ Needs Changes — 0 errors, 2 warnings, 1 suggestion.

The Helix payload construction, bounded device operations, manifest preservation, and final TRX publication are thoughtfully implemented. CI is green across all 44 reported checks; the prototype stage is default-off in that run, while the PR documents a separate successful opt-in parity run.

Before merge, align apkTestsHelixOnly with its advertised standalone behavior and keep caller-supplied exclude-test filtering active when only built-in exclusions are disabled. The target-minutes parameter should also be removed or given an observable effect.

Generated by Android PR Reviewer for #12479 · gpt56 · 139.1 AIC · ⌖ 9.04 AIC · ⊞ 25.7K
Comment /review to run again

displayName: APK Tests Helix Prototype
dependsOn:
- mac_build
condition: and(succeeded(), or(ne(variables['SkipTestStages'], 'true'), eq('${{ parameters.apkTestsHelixOnly }}', 'true')))

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.

🤖 ⚠️ MSBuild targets — apkTestsHelixOnly does not actually make this a prototype-only run. It only allows this stage to run when SkipTestStages was separately set by skipTests=true; selecting apkTestsHelixOnly=true by itself still leaves the package, Linux, MSBuild, emulator, Java.Interop, Android tools, and MAUI stages enabled. Please either derive SkipTestStages from this flag (guarded by the prototype being enabled) or rename/document this as a companion override that requires both flags.

(Rule: Pipeline conditions must match advertised behavior)

Log.Info (LogTag, $"Excluding test: {name}");
}
}
foreach (var name in GetListExtra ("exclude-test")) {

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.

🤖 ⚠️ Testing — This places the caller-supplied exclude-test extra under noexclusions, even though the log message says that switch skips only built-in exclusions and the existing caller-supplied exclude extra below is intentionally applied outside this block. Consequently, -e noexclusions true -e exclude-test Foo silently runs Foo. Please move this loop alongside the other command-line exclusion handling.

(Rule: Test option semantics)

[string] $WorkItemOS = 'windows',

[ValidateRange(1, 1440)]
[int] $TargetMinutes = 15

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.

🤖 💡 YAGNI — TargetMinutes is accepted and exposed as a pipeline knob, but its only use is writing the value to work-item-generation.json; it does not affect partitioning, scheduling, or timeout behavior. That makes changing apkTestsHelixTargetMinutes operationally inert. Since the current design deliberately keeps each flavor indivisible, consider removing this parameter until it controls behavior (or wire it into the relevant policy).

(Rule: Remove unused configuration)

@mmitche

mmitche commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

I think we could split this up as:

  • Lane 1, current setup: Debug w/ fast dev, a single Release-mode aab run
  • Lane 2, helix: All other combinations

We might currently have multiple aab files, but we could reduce down to a single one on Lane 1 with Release mode w/ default configuration.

I'd be OK taking out of draft and merging, if it's working in this way.

I still need to look into this a bit further. Not convined it's where we want to be yet.

This branch has not been deployed

No deployments
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.

3 participants