Skip to content

[tests] Verbose apkdiff output for BuildReleaseArm64 regressions - #11758

Merged
jonathanpeppers merged 2 commits into
dotnet:mainfrom
simonrozsival:apkdiff-verbose-regression-output
Jun 26, 2026
Merged

[tests] Verbose apkdiff output for BuildReleaseArm64 regressions#11758
jonathanpeppers merged 2 commits into
dotnet:mainfrom
simonrozsival:apkdiff-verbose-regression-output

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

When the BuildReleaseArm64 APK size regression check fails, the only feedback was:

apkdiff regression test failed with exit code: 3. See test attachments.
Expected: True
But was:  False

The referenced apkdiff.log (which holds the actual apkdiff report) was written to the build directory but never added as a test attachment — so "See test attachments" was misleading, and diagnosing a regression meant digging through the CI build artifacts archive.

This PR makes the failure self-explanatory and copy-paste-ready, so the regression details (and the updated reference) can go straight into a PR without hunting through CI artifacts.

Changes

  • BaseTest.csRunApkDiffCommand: now adds apkdiff.log (the full apkdiff stdout/stderr report) as a real TestContext.AddTestAttachment.
  • BuildTest2.csBuildReleaseArm64: on a regression (exit code != 0), replaces the terse Assert.IsTrue with an Assert.Fail that includes:
    • the full apkdiff output (stdout + stderr);
    • a per-entry .apkdesc diff (reference → current) with "Size" -/+ lines and (±N bytes) deltas, plus [added]/[removed] entries, so the regression is obvious at a glance;
    • the raw, full contents of the current .apkdesc, so it can be copy-pasted directly into the reference file and committed without downloading any attachment.
    • The freshly-generated .apkdesc is also added as an attachment.
    • The detailed message is built only on failure (no overhead on the passing path).
  • Added small GetApkDescDiff / ReadApkDescEntries helpers using System.Text.Json (already used elsewhere in this test project).

Example output on failure

apkdiff regression test failed with exit code: 3.

== apkdiff output ==
Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
  +      47,648 classes.dex
Error: apkdiff: File 'classes.dex' has changed by 47,648 bytes (10.59 %). This exceeds the threshold of 5.00 %.
  -      61,344 lib/arm64-v8a/libassembly-store.so
Summary:
  +      47,648 Dalvik executables 11.84% (of 402,352)
  -      61,344 Shared libraries -0.44% (of 14,089,448)
Error: apkdiff: Size regression occured, 1 check(s) failed.

== .apkdesc diff (reference -> current) ==
  classes.dex
-     "Size": 402352
+     "Size": 450000   (+47,648 bytes)
  lib/arm64-v8a/libassembly-store.so
-     "Size": 3461344
+     "Size": 3400000   (-61,344 bytes)

== current 'BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc' (copy/paste to update the reference) ==
{
  "Comment": null,
  "Entries": {
    "AndroidManifest.xml": {
      "Size": 3036
    },
    ...
  }
}

If this change is intended, update the reference '...apkdesc' with the current '.apkdesc' above (or attached to this test), or run build-tools/scripts/UpdateApkSizeReference.sh.

Notes

  • Test-infrastructure only — no product code, dependency, or threshold changes; the passing path is unchanged.
  • See Documentation/project-docs/ApkSizeRegressionChecks.md for background on these checks.

When the `BuildReleaseArm64` APK size regression check fails, the test
only reported:

    apkdiff regression test failed with exit code: 3. See test attachments.
    Expected: True
    But was:  False

...but the referenced `apkdiff.log` was written to the build directory
and never actually added as a test attachment, so diagnosing a
regression meant digging through CI build artifacts.

This makes the failure self-explanatory and copy-paste-ready:

* `RunApkDiffCommand` now adds `apkdiff.log` (the full apkdiff report)
  as a real test attachment.
* On failure, `BuildReleaseArm64` now fails with a detailed message
  containing the full apkdiff output (stdout + stderr) and a per-entry
  `.apkdesc` diff (reference -> current) with `"Size"` +/- lines and
  byte deltas, so the regression is obvious at a glance.
* The freshly-generated `.apkdesc` is attached too, so the updated
  reference can be grabbed straight from the test results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 26, 2026 14:52

Copilot AI 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.

Pull request overview

Improves diagnosability of BuildReleaseArm64 APK size regression failures by ensuring the apkdiff report is surfaced as a real test attachment and by emitting a copy/paste-friendly failure message that includes both apkdiff output and a focused per-entry .apkdesc size diff.

Changes:

  • Attach apkdiff.log produced by RunApkDiffCommand via TestContext.AddTestAttachment.
  • Replace the terse assertion in BuildReleaseArm64 with an on-failure Assert.Fail message that includes full apkdiff output plus a computed .apkdesc per-entry size diff.
  • Attach the newly generated .apkdesc on failure to make reference updates easier.
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs Adds apkdiff.log as a test attachment (with a suggested tweak to only attach on failure).
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs Enhances BuildReleaseArm64 regression failures with verbose output, .apkdesc diff helpers, and attaches the generated .apkdesc on failure.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Print the full contents of the freshly-generated `.apkdesc` in the
failure message so it can be copy-pasted directly into the reference
`.apkdesc` file and committed, without downloading the test attachment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@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

Nice quality-of-life improvement — turning the opaque apkdiff regression test failed ... See test attachments into a self-contained, copy-paste-ready failure message is genuinely useful, and the per-entry .apkdesc diff (reference → current with ±N bytes deltas) is a great touch. A couple of items before merge:

Issues

  • ⚠️ Error handling (1) — the JSON parsing in GetApkDescDiff / ReadApkDescEntries can throw (JsonException, GetInt64) and clobber the very diagnostic message it exists to produce. See inline on BuildTest2.cs.
  • 💡 Testing (1) — apkdiff.log is now attached on the success path too; confirm that's intended vs. failure-only. See inline on BaseTest.cs.

Verified (no action needed)

  • System.Text.Json is already referenced by other tests in this project (TrimmableTypeMapBuildTests.cs, JavaDependencyVerificationTests.cs), and .IsNullOrEmpty () resolves via the existing using Xamarin.Android.Tasks; — both compile cleanly.
  • The {(newSize - oldSize):+#,0;-#,0;0} format is correct: the negative section formats the absolute value with a literal -, so deltas render as +47,648 / -61,344.
  • The added/removed/changed key logic in GetApkDescDiff is sound (ordinal Union + TryGetValue on both sides; JsonDocument is disposed via using var).

CI

  • The suite that exercises the modified test — MSBuild Tests macOS (1–10) — is fully green, so BuildReleaseArm64 builds and passes with these changes.
  • One unrelated-looking failure: Package Tests macOS → APKs 1 (a different suite, not the modified test). Please confirm it's a known flake / re-run it. Several other legs are still in progress, so CI isn't green yet.

Test-infra only — no product, dependency, or threshold changes. Thanks! 🙏

Generated by Android PR Reviewer for issue #11758 · 665.7 AIC · ⌖ 47.1 AIC · ⊞ 40K
Comment /review to run again

static Dictionary<string, long> ReadApkDescEntries (string path)
{
var result = new Dictionary<string, long> (StringComparer.Ordinal);
using var doc = JsonDocument.Parse (File.ReadAllText (path));

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.

🤖 ⚠️ Error handlingReadApkDescEntries calls JsonDocument.Parse (throws JsonException on malformed/truncated JSON) and size.GetInt64 () (throws if Size isn't a number or overflows long). GetApkDescDiff runs at line 334 — before the rest of the failure message is assembled — so any throw here propagates out of the test and replaces the carefully-built diagnostic (apkdiff output and the copy-paste .apkdesc) with an opaque stack trace, defeating the whole point of this change.

The current .apkdesc is freshly generated and could be empty/truncated if apkdiff bailed early. Consider wrapping the parse in a try/catch inside GetApkDescDiff and returning a fallback string, so the per-entry diff is best-effort and never clobbers the real failure message.

(Rule: Diagnostic/error paths must degrade gracefully and never mask the original failure)

@jonathanpeppers jonathanpeppers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Test failures unrelated

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants