From 1a9c44bb6a1bd90533ec8d9674a6ce54ae9ce185 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 20 May 2026 10:18:21 +0200 Subject: [PATCH 1/6] [tests] Ignore the AppSize tests for now. It seems that the results depend on the macOS version of the machine executing the tests. That's unfortunate when all my macs are at 26.5 and the bots are at 26.2, because I can't re-generate the expected app sizes locally. So ignore these tests until I can come up with a better solution. --- tests/dotnet/UnitTests/AppSizeTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index e3157fd6dbe..047d3613c45 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -8,6 +8,7 @@ namespace Xamarin.Tests { [TestFixture] + [Ignore ("The results depend on the macOS version of the bot running the test")] public class AppSizeTest : TestBaseClass { [TestCase (ApplePlatform.iOS, "ios-arm64")] From 3d45cc4eb343e9a2e0d01bf0828b19e4d96946cb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 20 May 2026 10:22:43 +0200 Subject: [PATCH 2/6] [tests] Generate patch files for app size test failures When WRITE_KNOWN_FAILURES is not set, instead of telling users to set the environment variable and re-run, generate a unified diff patch file that can be applied to update the expected files. The patch file is stored in a temporary directory (via Cache.CreateTemporaryDirectory) and uploaded as an Azure DevOps artifact via ##vso[artifact.upload]. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/update-expected-app-size/SKILL.md | 122 ++++++++++++++++++ tests/dotnet/UnitTests/AppSizeTest.cs | 28 +++- 2 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 .github/skills/update-expected-app-size/SKILL.md diff --git a/.github/skills/update-expected-app-size/SKILL.md b/.github/skills/update-expected-app-size/SKILL.md new file mode 100644 index 00000000000..0277bfcb128 --- /dev/null +++ b/.github/skills/update-expected-app-size/SKILL.md @@ -0,0 +1,122 @@ +--- +name: update-expected-app-size +description: >- + Download updated expected app size files from Azure DevOps CI artifacts for + the current branch. Use when app size tests fail in CI and the user wants to + update the expected files locally. Trigger on "update app size", "download + expected files", "fix app size test", or "update expected app size files". +--- + +# Update Expected App Size Files + +Download updated expected app size files from Azure DevOps artifacts for the current branch's PR build, and apply them to the repository. + +## When to Use + +- App size tests failed in CI and the user wants to update the expected files +- User asks to "update app size", "download expected files", or "fix app size test failures" +- User wants to pull the updated expected files from a recent CI build + +## Background + +The app size tests (`tests/dotnet/UnitTests/AppSizeTest.cs`) compare the built app's size and preserved APIs against expected files stored in `tests/dotnet/UnitTests/expected/`. When the test detects a difference and `WRITE_KNOWN_FAILURES` is not set, it uploads the updated expected file as an Azure DevOps artifact named `updated-expected-sizes`. + +Individual artifact names follow these patterns: +- `{Platform}-{Runtime}-size` — e.g., `iOS-MonoVM-size`, `MacCatalyst-NativeAOT-size` +- `{Platform}-{Runtime}-preservedapis` — e.g., `iOS-MonoVM-preservedapis` + +The expected files on disk are at: +- `tests/dotnet/UnitTests/expected/{name}-size.txt` +- `tests/dotnet/UnitTests/expected/{name}-preservedapis.txt` + +## Workflow + +### 1. Determine the current branch and PR + +```bash +BRANCH=$(git branch --show-current) +# Find the PR number for this branch +gh pr list --head "$BRANCH" --repo dotnet/macios --json number,url --jq '.[0]' +``` + +If no PR is found, inform the user that this skill requires a PR to exist for the current branch (so that CI has run). + +### 2. Find the Azure DevOps build + +The CI builds for PRs in dotnet/macios run in the `devdiv` Azure DevOps organization, project `DevDiv`. + +Use the GitHub PR checks to find the Azure DevOps build URL: + +```bash +gh pr checks --repo dotnet/macios +``` + +Look for a check that links to Azure DevOps. The build URL will look like: +``` +https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=XXXXXXX +``` + +Extract the `buildId` from the URL. + +### 3. Download the artifacts + +Use the Azure DevOps REST API to list and download artifacts: + +```bash +# List artifacts for the build +curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?api-version=7.0" \ + -H "Authorization: Bearer $(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" +``` + +Look for artifacts in the `updated-expected-sizes` container folder. Download each one: + +```bash +# Download a specific artifact +curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?artifactName={artifactName}&api-version=7.0&%24format=zip" \ + -H "Authorization: Bearer $(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" \ + -o artifact.zip +``` + +If `az` is not available or not authenticated, direct the user to download manually from the Azure DevOps build artifacts page. + +### 4. Place the files + +Extract the downloaded artifacts and place them in the expected directory: + +```bash +EXPECTED_DIR="tests/dotnet/UnitTests/expected" +``` + +Map artifact names to file names: +- Artifact `{name}-size` → file `{name}-size.txt` +- Artifact `{name}-preservedapis` → file `{name}-preservedapis.txt` + +### 5. Verify and commit + +After placing the files: +1. Run `git diff` to show what changed +2. Ask the user if the changes look correct +3. If confirmed, commit the changes: + ```bash + git add tests/dotnet/UnitTests/expected/ + git commit -m "[tests] Update expected app size files" + ``` + +## Fallback: Manual Download + +If automated download fails (auth issues, etc.), provide the user with: +1. The Azure DevOps build URL +2. Instructions to navigate to the build → Summary → Artifacts section +3. Look for individual artifacts whose names match the patterns above +4. Download each file and place it as `tests/dotnet/UnitTests/expected/{artifactName}.txt` + +## Fallback: Run Locally + +If the user can build locally, they can update the expected files directly: + +```bash +WRITE_KNOWN_FAILURES=1 tests-dotnet AppSizeTest +``` + +This runs the tests, updates the expected files in place, and marks the tests as passed. + diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index 047d3613c45..e7eb24bb4e5 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -8,7 +8,6 @@ namespace Xamarin.Tests { [TestFixture] - [Ignore ("The results depend on the macOS version of the bot running the test")] public class AppSizeTest : TestBaseClass { [TestCase (ApplePlatform.iOS, "ios-arm64")] @@ -114,7 +113,8 @@ void Run (ApplePlatform platform, string runtimeIdentifiers, string configuratio DotNet.AssertBuild (project_path, properties); // FORCE_UPDATE_KNOWN_FAILURES will update the known failures files even if the test doesn't actually fail - // WRITE_KNOWN_FAILURES will only update the known failures files if the test fails + // WRITE_KNOWN_FAILURES will only update the known failures files if the test fails (and mark the test as passed) + // If neither is set, the updated expected file is uploaded as an Azure DevOps artifact. var forceUpdate = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("FORCE_UPDATE_KNOWN_FAILURES")); var update = forceUpdate || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("WRITE_KNOWN_FAILURES")); @@ -174,7 +174,8 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, msg += " Check the modified files for more information."; updated = true; } else if (!withinTolerance) { - msg += " Set the environment variable WRITE_KNOWN_FAILURES=1, run the test again, and verify the modified files for more information."; + UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); + msg += " The updated expected file has been uploaded as an Azure DevOps artifact."; } Console.WriteLine ($" {msg}"); @@ -186,11 +187,11 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, if (!expectedLines.TryGetValue (key, out var expectedLine)) { Console.WriteLine ($" File '{key}' was added to app bundle: {actualLines [key]}"); if (!updated) - Assert.Fail ($"The file '{key}' was added to the app bundle."); + Assert.Fail ($"The file '{key}' was added to the app bundle. The updated expected file has been uploaded as an artifact."); } else if (!actualLines.TryGetValue (key, out var actualLine)) { Console.WriteLine ($" File '{key}' was removed from app bundle: {expectedLine}"); if (!updated) - Assert.Fail ($"The file '{key}' was removed from the app bundle."); + Assert.Fail ($"The file '{key}' was removed from the app bundle. The updated expected file has been uploaded as an artifact."); } else if (expectedLine != actualLine) { Console.WriteLine ($" File '{key}' changed in app bundle:"); Console.WriteLine ($" -{expectedLine}"); @@ -238,11 +239,24 @@ void AssertAssemblyReport (ApplePlatform platform, string name, string appPath, } if (!update) { - Assert.That (addedAPIs, Is.Empty, "No added APIs (set the environment variable WRITE_KNOWN_FAILURES=1 and run the test again to update the expected set of APIs)"); - Assert.That (removedAPIs, Is.Empty, "No removed APIs (set the environment variable WRITE_KNOWN_FAILURES=1 and run the test again to update the expected set of APIs)"); + if (addedAPIs.Count > 0 || removedAPIs.Count > 0) { + UploadUpdatedExpectedFile (expectedFile, string.Join ('\n', preservedAPIs) + "\n"); + var updateMsg = " The updated expected file has been uploaded as an artifact."; + Assert.That (addedAPIs, Is.Empty, "No added APIs." + updateMsg); + Assert.That (removedAPIs, Is.Empty, "No removed APIs." + updateMsg); + } } } + static void UploadUpdatedExpectedFile (string expectedFilePath, string content) + { + var fileName = Path.GetFileName (expectedFilePath); + var tmpDir = Cache.CreateTemporaryDirectory ("AppSizeTest"); + var tmpFile = Path.Combine (tmpDir, fileName); + File.WriteAllText (tmpFile, content); + Console.WriteLine ($"##vso[artifact.upload containerfolder=updated-expected-sizes;artifactname={Path.GetFileNameWithoutExtension (fileName)}]{tmpFile}"); + } + static string FormatBytes (long bytes, bool alwaysShowSign = false) { return $"{(alwaysShowSign && bytes > 0 ? "+" : "")}{bytes:N0} bytes ({bytes / 1024.0:N1} KB = {bytes / (1024.0 * 1024.0):N1} MB)"; From ff766b0e85f577c721fec1f21e5c00a13f1c3bbd Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 20 May 2026 11:08:42 +0200 Subject: [PATCH 3/6] Change a few expected files to trigger an app size test failure. --- .../expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt | 1 - tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt | 1 - .../MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt | 1 - .../dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt | 1 - .../UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt | 1 - tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt | 2 +- tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt | 1 - 7 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt index 6ec14abe043..1fe5bd83c09 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt @@ -82,7 +82,6 @@ Microsoft.MacCatalyst.dll:Foundation.NSAutoreleasePool.get_ClassHandle() Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Ascending Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Descending -Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Same Microsoft.MacCatalyst.dll:Foundation.NSDictionary Microsoft.MacCatalyst.dll:Foundation.NSDictionary Foundation.NSDictionary/d__66::<>4__this Microsoft.MacCatalyst.dll:Foundation.NSDictionary._Xamarin_ConstructINativeObject(ObjCRuntime.NativeHandle, System.Boolean) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt index 67e1e583c97..b4d9a0b1173 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt @@ -1,4 +1,3 @@ -AppBundleSize: 16,323,580 bytes (15,941.0 KB = 15.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 4,134 bytes (4.0 KB = 0.0 MB) Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt index 067f24eb504..8a39a37c695 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt @@ -166,7 +166,6 @@ Contents/MonoBundle/.xamarin/osx-arm64/System.Threading.Timer.dll: 15,624 bytes Contents/MonoBundle/.xamarin/osx-arm64/System.Transactions.dll: 17,168 bytes (16.8 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Transactions.Local.dll: 401,208 bytes (391.8 KB = 0.4 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.ValueTuple.dll: 16,144 bytes (15.8 KB = 0.0 MB) -Contents/MonoBundle/.xamarin/osx-arm64/System.Web.dll: 15,664 bytes (15.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Web.HttpUtility.dll: 57,144 bytes (55.8 KB = 0.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Windows.dll: 16,144 bytes (15.8 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.dll: 23,824 bytes (23.3 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt index 50aaf0f31b2..b89e2c0ad44 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt @@ -1,5 +1,4 @@ AppBundleSize: 3,616,420 bytes (3,531.7 KB = 3.4 MB) -# The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,999 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt index ce409263e27..4955c79e338 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt @@ -5,4 +5,3 @@ archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,889 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 7,916,032 bytes (7,730.5 KB = 7.5 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt index 0c9c253439b..1d0bf54106f 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt @@ -1,4 +1,4 @@ -AppBundleSize: 3,602,873 bytes (3,518.4 KB = 3.4 MB) +AppBundleSize: 3,602 bytes (3,518.4 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,997 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt index 12ee378aba9..d7b2b24a68b 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt @@ -2,7 +2,6 @@ AppBundleSize: 2,783,868 bytes (2,718.6 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) SizeTestApp: 2,777,936 bytes (2,712.8 KB = 2.6 MB) From f39ef8299f7e64454d810d520dc4c863db68efd4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 20 May 2026 18:54:02 +0200 Subject: [PATCH 4/6] [tests] Write expected files to ArtifactStagingDirectory for pipeline upload The ##vso[artifact.upload] command doesn't work inside NUnit test runners because the test framework captures stdout. Instead, write files to $(Build.ArtifactStagingDirectory)/updated-expected-sizes/ and add a PublishPipelineArtifact step that uploads the directory after tests complete. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/update-expected-app-size/SKILL.md | 36 ++++++++++--------- tests/dotnet/UnitTests/AppSizeTest.cs | 23 +++++++----- .../automation/templates/tests/run-tests.yml | 9 +++++ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/.github/skills/update-expected-app-size/SKILL.md b/.github/skills/update-expected-app-size/SKILL.md index 0277bfcb128..88d888c17a0 100644 --- a/.github/skills/update-expected-app-size/SKILL.md +++ b/.github/skills/update-expected-app-size/SKILL.md @@ -19,15 +19,15 @@ Download updated expected app size files from Azure DevOps artifacts for the cur ## Background -The app size tests (`tests/dotnet/UnitTests/AppSizeTest.cs`) compare the built app's size and preserved APIs against expected files stored in `tests/dotnet/UnitTests/expected/`. When the test detects a difference and `WRITE_KNOWN_FAILURES` is not set, it uploads the updated expected file as an Azure DevOps artifact named `updated-expected-sizes`. +The app size tests (`tests/dotnet/UnitTests/AppSizeTest.cs`) compare the built app's size and preserved APIs against expected files stored in `tests/dotnet/UnitTests/expected/`. When the test detects a difference and `WRITE_KNOWN_FAILURES` is not set, it writes the updated expected file to `$(Build.ArtifactStagingDirectory)/updated-expected-sizes/`, and a pipeline step publishes this directory as a build artifact. -Individual artifact names follow these patterns: -- `{Platform}-{Runtime}-size` — e.g., `iOS-MonoVM-size`, `MacCatalyst-NativeAOT-size` -- `{Platform}-{Runtime}-preservedapis` — e.g., `iOS-MonoVM-preservedapis` +The artifact name follows the pattern `updated-expected-sizes-{testPrefix}-{attempt}` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Inside the artifact, files are named: +- `{Platform}-{Runtime}-size.txt` — e.g., `iOS-MonoVM-size.txt` +- `{Platform}-{Runtime}-preservedapis.txt` — e.g., `iOS-MonoVM-preservedapis.txt` The expected files on disk are at: -- `tests/dotnet/UnitTests/expected/{name}-size.txt` -- `tests/dotnet/UnitTests/expected/{name}-preservedapis.txt` +- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-size.txt` +- `tests/dotnet/UnitTests/expected/{Platform}-{Runtime}-preservedapis.txt` ## Workflow @@ -64,32 +64,36 @@ Use the Azure DevOps REST API to list and download artifacts: ```bash # List artifacts for the build +TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv) curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?api-version=7.0" \ - -H "Authorization: Bearer $(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" + -H "Authorization: Bearer $TOKEN" ``` -Look for artifacts in the `updated-expected-sizes` container folder. Download each one: +Look for artifacts whose names contain `updated-expected-sizes` (e.g., `updated-expected-sizes-dotnettests_ios-1`). Get the artifact's `downloadUrl` and download it: ```bash -# Download a specific artifact -curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?artifactName={artifactName}&api-version=7.0&%24format=zip" \ - -H "Authorization: Bearer $(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" \ - -o artifact.zip +# Get the download URL for a specific artifact +ARTIFACT_INFO=$(curl -s "https://devdiv.visualstudio.com/DevDiv/_apis/build/builds/{buildId}/artifacts?artifactName={artifactName}&api-version=7.0" \ + -H "Authorization: Bearer $TOKEN") +DOWNLOAD_URL=$(echo "$ARTIFACT_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['resource']['downloadUrl'])") + +# Download the artifact zip +curl -sL "$DOWNLOAD_URL" -H "Authorization: Bearer $TOKEN" -o artifact.zip ``` If `az` is not available or not authenticated, direct the user to download manually from the Azure DevOps build artifacts page. ### 4. Place the files -Extract the downloaded artifacts and place them in the expected directory: +Extract the downloaded artifact zip and place the files in the expected directory: ```bash EXPECTED_DIR="tests/dotnet/UnitTests/expected" +unzip -o artifact.zip -d /tmp/updated-sizes/ +cp /tmp/updated-sizes/*/*.txt "$EXPECTED_DIR/" ``` -Map artifact names to file names: -- Artifact `{name}-size` → file `{name}-size.txt` -- Artifact `{name}-preservedapis` → file `{name}-preservedapis.txt` +The files inside the zip already have the correct names (e.g., `iOS-MonoVM-size.txt`) and can be copied directly. ### 5. Verify and commit diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index e7eb24bb4e5..4e58c31d54d 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -175,7 +175,7 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, updated = true; } else if (!withinTolerance) { UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); - msg += " The updated expected file has been uploaded as an Azure DevOps artifact."; + msg += " The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; } Console.WriteLine ($" {msg}"); @@ -187,11 +187,11 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, if (!expectedLines.TryGetValue (key, out var expectedLine)) { Console.WriteLine ($" File '{key}' was added to app bundle: {actualLines [key]}"); if (!updated) - Assert.Fail ($"The file '{key}' was added to the app bundle. The updated expected file has been uploaded as an artifact."); + Assert.Fail ($"The file '{key}' was added to the app bundle. The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); } else if (!actualLines.TryGetValue (key, out var actualLine)) { Console.WriteLine ($" File '{key}' was removed from app bundle: {expectedLine}"); if (!updated) - Assert.Fail ($"The file '{key}' was removed from the app bundle. The updated expected file has been uploaded as an artifact."); + Assert.Fail ($"The file '{key}' was removed from the app bundle. The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); } else if (expectedLine != actualLine) { Console.WriteLine ($" File '{key}' changed in app bundle:"); Console.WriteLine ($" -{expectedLine}"); @@ -241,7 +241,7 @@ void AssertAssemblyReport (ApplePlatform platform, string name, string appPath, if (!update) { if (addedAPIs.Count > 0 || removedAPIs.Count > 0) { UploadUpdatedExpectedFile (expectedFile, string.Join ('\n', preservedAPIs) + "\n"); - var updateMsg = " The updated expected file has been uploaded as an artifact."; + var updateMsg = " The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; Assert.That (addedAPIs, Is.Empty, "No added APIs." + updateMsg); Assert.That (removedAPIs, Is.Empty, "No removed APIs." + updateMsg); } @@ -251,10 +251,17 @@ void AssertAssemblyReport (ApplePlatform platform, string name, string appPath, static void UploadUpdatedExpectedFile (string expectedFilePath, string content) { var fileName = Path.GetFileName (expectedFilePath); - var tmpDir = Cache.CreateTemporaryDirectory ("AppSizeTest"); - var tmpFile = Path.Combine (tmpDir, fileName); - File.WriteAllText (tmpFile, content); - Console.WriteLine ($"##vso[artifact.upload containerfolder=updated-expected-sizes;artifactname={Path.GetFileNameWithoutExtension (fileName)}]{tmpFile}"); + var artifactStagingDir = Environment.GetEnvironmentVariable ("BUILD_ARTIFACTSTAGINGDIRECTORY"); + string outputDir; + if (!string.IsNullOrEmpty (artifactStagingDir)) { + outputDir = Path.Combine (artifactStagingDir, "updated-expected-sizes"); + } else { + outputDir = Path.Combine (Cache.CreateTemporaryDirectory ("AppSizeTest"), "updated-expected-sizes"); + } + Directory.CreateDirectory (outputDir); + var outputFile = Path.Combine (outputDir, fileName); + File.WriteAllText (outputFile, content); + Console.WriteLine ($" Updated expected file written to: {outputFile}"); } static string FormatBytes (long bytes, bool alwaysShowSign = false) diff --git a/tools/devops/automation/templates/tests/run-tests.yml b/tools/devops/automation/templates/tests/run-tests.yml index 188474aa28e..40c85db23a1 100644 --- a/tools/devops/automation/templates/tests/run-tests.yml +++ b/tools/devops/automation/templates/tests/run-tests.yml @@ -172,6 +172,15 @@ steps: continueOnError: true condition: succeededOrFailed() +# Upload updated expected app size files if the app size tests produced any. +- task: PublishPipelineArtifact@1 + displayName: 'Publish Artifact: Updated expected app size files' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)/updated-expected-sizes' + artifactName: '${{ parameters.uploadPrefix }}updated-expected-sizes-${{ parameters.testPrefix }}-$(System.JobAttempt)' + continueOnError: true + condition: succeededOrFailed() + - pwsh: | $summaryName = "TestSummary-${{ parameters.testPrefix }}.md" $summaryPath = "$Env:SYSTEM_DEFAULTWORKINGDIRECTORY/$(BUILD_REPOSITORY_TITLE)/tests/TestSummary.md" From 6a67676d723a044853c1d2b8f638f3c33363164a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 20 May 2026 21:07:52 +0200 Subject: [PATCH 5/6] Fix artifact upload when file list changes within size tolerance Previously, UploadUpdatedExpectedFile was only called when the total app size exceeded the tolerance. If files were added/removed from the bundle but the total size stayed within tolerance, no artifact was uploaded and the test would fail with a misleading message saying the artifact was available (when it wasn't). Restructure AssertAppSize to: 1. Detect both size differences AND file-list differences 2. Upload the updated expected file for ANY meaningful difference 3. Produce a clear failure message distinguishing file-list changes from size changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/dotnet/UnitTests/AppSizeTest.cs | 43 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/tests/dotnet/UnitTests/AppSizeTest.cs b/tests/dotnet/UnitTests/AppSizeTest.cs index 4e58c31d54d..e13e098c43c 100644 --- a/tests/dotnet/UnitTests/AppSizeTest.cs +++ b/tests/dotnet/UnitTests/AppSizeTest.cs @@ -167,31 +167,21 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, msg = $"App size changed significantly ({FormatBytes (appSizeDifference, true)} different > tolerance of +-{FormatBytes (toleranceInBytes)}). Expected app size: {FormatBytes (expectedAppBundleSize)}, actual app size: {FormatBytes (appBundleSize)}."; } - var updated = false; - if (forceUpdate || (update && !withinTolerance)) { - Directory.CreateDirectory (expectedDirectory); - File.WriteAllText (expectedSizeReportPath, report.ToString ()); - msg += " Check the modified files for more information."; - updated = true; - } else if (!withinTolerance) { - UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); - msg += " The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."; - } - Console.WriteLine ($" {msg}"); + // Compare individual files in the app bundle var expectedLines = expectedSizeReport.SplitLines ().Skip (2).Where (v => v.IndexOf (':') >= 0).ToDictionary (v => v [..v.IndexOf (':')], v => v [(v.IndexOf (':') + 1)..]); var actualLines = report.ToString ().SplitLines ().Skip (2).Where (v => v.IndexOf (':') >= 0).ToDictionary (v => v [..v.IndexOf (':')], v => v [(v.IndexOf (':') + 1)..]); var allKeys = expectedLines.Keys.Union (actualLines.Keys).OrderBy (v => v); + var filesAdded = new List (); + var filesRemoved = new List (); foreach (var key in allKeys) { if (!expectedLines.TryGetValue (key, out var expectedLine)) { Console.WriteLine ($" File '{key}' was added to app bundle: {actualLines [key]}"); - if (!updated) - Assert.Fail ($"The file '{key}' was added to the app bundle. The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + filesAdded.Add (key); } else if (!actualLines.TryGetValue (key, out var actualLine)) { Console.WriteLine ($" File '{key}' was removed from app bundle: {expectedLine}"); - if (!updated) - Assert.Fail ($"The file '{key}' was removed from the app bundle. The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + filesRemoved.Add (key); } else if (expectedLine != actualLine) { Console.WriteLine ($" File '{key}' changed in app bundle:"); Console.WriteLine ($" -{expectedLine}"); @@ -199,8 +189,27 @@ static void AssertAppSize (ApplePlatform platform, string name, string appPath, } } - if (!updated && !withinTolerance) - Assert.Fail (msg); + // Determine if there are any meaningful differences + var hasFileDifferences = filesAdded.Count > 0 || filesRemoved.Count > 0; + var hasSizeDifference = !withinTolerance; + var hasDifferences = hasFileDifferences || hasSizeDifference; + + if (forceUpdate || (update && hasDifferences)) { + Directory.CreateDirectory (expectedDirectory); + File.WriteAllText (expectedSizeReportPath, report.ToString ()); + Console.WriteLine ($" Updated expected file: {expectedSizeReportPath}"); + } else if (hasDifferences) { + UploadUpdatedExpectedFile (expectedSizeReportPath, report.ToString ()); + if (hasFileDifferences) { + var details = new List (); + foreach (var key in filesAdded) + details.Add ($"added: '{key}'"); + foreach (var key in filesRemoved) + details.Add ($"removed: '{key}'"); + Assert.Fail ($"The app bundle's file list changed ({string.Join (", ", details)}). The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + } + Assert.Fail ($"{msg} The updated expected file is available as a build artifact (set WRITE_KNOWN_FAILURES=1 to update locally)."); + } } // Create a file with all the APIs that survived the trimmer; this can be useful to determine what is not trimmed away. From 46e949c867b5ef379b3c9bd13ea1e0d3a935481d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 21 May 2026 10:02:20 +0200 Subject: [PATCH 6/6] Update expected app size files from CI build 14157636 Applied artifacts from all 4 platforms after the file-list tolerance fix. Changes include: - iOS: corrected truncated AppBundleSize + restored Info.plist entry - tvOS: restored comment line and SizeTestApp entry + updated sizes - MacCatalyst: added AppBundleSize header + NSComparisonResult.Same - macOS: restored System.Web.dll entry + updated sizes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MacCatalyst-MonoVM-interpreter-preservedapis.txt | 1 + .../dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt | 5 +++-- .../MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt | 7 ++++--- .../UnitTests/expected/TVOS-MonoVM-interpreter-size.txt | 7 ++++--- .../expected/TVOS-NativeAOT-TrimmableStatic-size.txt | 5 +++-- .../UnitTests/expected/iOS-MonoVM-interpreter-size.txt | 6 +++--- tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt | 5 +++-- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt index 1fe5bd83c09..6ec14abe043 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-interpreter-preservedapis.txt @@ -82,6 +82,7 @@ Microsoft.MacCatalyst.dll:Foundation.NSAutoreleasePool.get_ClassHandle() Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Ascending Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Descending +Microsoft.MacCatalyst.dll:Foundation.NSComparisonResult Foundation.NSComparisonResult::Same Microsoft.MacCatalyst.dll:Foundation.NSDictionary Microsoft.MacCatalyst.dll:Foundation.NSDictionary Foundation.NSDictionary/d__66::<>4__this Microsoft.MacCatalyst.dll:Foundation.NSDictionary._Xamarin_ConstructINativeObject(ObjCRuntime.NativeHandle, System.Boolean) diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt index b4d9a0b1173..b2abcecdbae 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-MonoVM-size.txt @@ -1,7 +1,8 @@ +AppBundleSize: 16,321,420 bytes (15,938.9 KB = 15.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 4,134 bytes (4.0 KB = 0.0 MB) -Contents/Info.plist: 1,101 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 13,812,384 bytes (13,488.7 KB = 13.2 MB) +Contents/Info.plist: 1,085 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 13,810,240 bytes (13,486.6 KB = 13.2 MB) Contents/MonoBundle/aot-instances.aotdata.arm64: 1,045,032 bytes (1,020.5 KB = 1.0 MB) Contents/MonoBundle/Microsoft.MacCatalyst.aotdata.arm64: 36,368 bytes (35.5 KB = 0.0 MB) Contents/MonoBundle/Microsoft.MacCatalyst.dll: 50,688 bytes (49.5 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt index 8a39a37c695..e639fde89ac 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 260,130,342 bytes (254,033.5 KB = 248.1 MB) +AppBundleSize: 260,129,862 bytes (254,033.1 KB = 248.1 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 67,868 bytes (66.3 KB = 0.1 MB) -Contents/Info.plist: 732 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 7,431,264 bytes (7,257.1 KB = 7.1 MB) +Contents/Info.plist: 716 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 7,430,800 bytes (7,256.6 KB = 7.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/_Microsoft.macOS.TypeMap.dll: 4,847,616 bytes (4,734.0 KB = 4.6 MB) Contents/MonoBundle/.xamarin/osx-arm64/_SizeTestApp.TypeMap.dll: 3,072 bytes (3.0 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll: 893,192 bytes (872.3 KB = 0.9 MB) @@ -166,6 +166,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/System.Threading.Timer.dll: 15,624 bytes Contents/MonoBundle/.xamarin/osx-arm64/System.Transactions.dll: 17,168 bytes (16.8 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Transactions.Local.dll: 401,208 bytes (391.8 KB = 0.4 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.ValueTuple.dll: 16,144 bytes (15.8 KB = 0.0 MB) +Contents/MonoBundle/.xamarin/osx-arm64/System.Web.dll: 15,664 bytes (15.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Web.HttpUtility.dll: 57,144 bytes (55.8 KB = 0.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Windows.dll: 16,144 bytes (15.8 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.dll: 23,824 bytes (23.3 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt index b89e2c0ad44..4a3f7319371 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-MonoVM-interpreter-size.txt @@ -1,11 +1,12 @@ -AppBundleSize: 3,616,420 bytes (3,531.7 KB = 3.4 MB) +AppBundleSize: 3,616,596 bytes (3,531.8 KB = 3.4 MB) +# The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,999 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,104 bytes (1.1 KB = 0.0 MB) Microsoft.tvOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 2,404,416 bytes (2,348.1 KB = 2.3 MB) +SizeTestApp: 2,404,608 bytes (2,348.2 KB = 2.3 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 41,312 bytes (40.3 KB = 0.0 MB) System.Private.CoreLib.dll: 988,160 bytes (965.0 KB = 0.9 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt index 4955c79e338..e01435700c4 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-TrimmableStatic-size.txt @@ -1,7 +1,8 @@ -AppBundleSize: 7,922,022 bytes (7,736.3 KB = 7.6 MB) +AppBundleSize: 7,922,198 bytes (7,736.5 KB = 7.6 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,120 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,104 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,889 bytes (1.8 KB = 0.0 MB) +SizeTestApp: 7,916,224 bytes (7,730.7 KB = 7.5 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt index 1d0bf54106f..93230234dc1 100644 --- a/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-size.txt @@ -1,12 +1,12 @@ -AppBundleSize: 3,602 bytes (3,518.4 KB = 3.4 MB) +AppBundleSize: 3,603,385 bytes (3,518.9 KB = 3.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 3,997 bytes (3.9 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,143 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,127 bytes (1.1 KB = 0.0 MB) Microsoft.iOS.dll: 154,624 bytes (151.0 KB = 0.1 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,405 bytes (1.4 KB = 0.0 MB) -SizeTestApp: 2,390,848 bytes (2,334.8 KB = 2.3 MB) +SizeTestApp: 2,391,376 bytes (2,335.3 KB = 2.3 MB) SizeTestApp.dll: 7,680 bytes (7.5 KB = 0.0 MB) System.Private.CoreLib.aotdata.arm64: 41,312 bytes (40.3 KB = 0.0 MB) System.Private.CoreLib.dll: 988,160 bytes (965.0 KB = 0.9 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt index d7b2b24a68b..5cc462cd43e 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt @@ -1,7 +1,8 @@ -AppBundleSize: 2,783,868 bytes (2,718.6 KB = 2.7 MB) +AppBundleSize: 2,784,380 bytes (2,719.1 KB = 2.7 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) +Info.plist: 1,127 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,777,936 bytes (2,712.8 KB = 2.6 MB) +SizeTestApp: 2,778,464 bytes (2,713.3 KB = 2.6 MB)