diff --git a/build-tools/automation/ApkTestsHelix.proj b/build-tools/automation/ApkTestsHelix.proj
new file mode 100644
index 00000000000..efbd28c0b9e
--- /dev/null
+++ b/build-tools/automation/ApkTestsHelix.proj
@@ -0,0 +1,32 @@
+
+
+ msbuild
+ false
+ true
+ tests/android/apk-tests/
+ Android APK Tests - Helix
+ 00:30:00
+
+
+
+
+
+
+ $(HelixTestRunName)
+
+
+ $(ApkTestsHelixWorkItemTimeout)
+
+
+
+
+
+
+
+
+
+
diff --git a/build-tools/automation/apk-tests-helix/ApkTestsHelix.Tests.ps1 b/build-tools/automation/apk-tests-helix/ApkTestsHelix.Tests.ps1
new file mode 100644
index 00000000000..80bdb45f0c5
--- /dev/null
+++ b/build-tools/automation/apk-tests-helix/ApkTestsHelix.Tests.ps1
@@ -0,0 +1,69 @@
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+function Assert-Equal
+{
+ param (
+ [Parameter(Mandatory)]
+ [object] $Expected,
+
+ [Parameter(Mandatory)]
+ [object] $Actual,
+
+ [Parameter(Mandatory)]
+ [string] $Message
+ )
+
+ if ($Expected -ne $Actual) {
+ throw "$Message Expected '$Expected', got '$Actual'."
+ }
+}
+
+$root = Join-Path $PSScriptRoot '.test-output'
+Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction Ignore
+try {
+ $workItem = Join-Path $root 'work-items\sample'
+ $platformTools = Join-Path $root 'platform-tools'
+ New-Item -ItemType Directory -Force -Path $workItem, $platformTools | Out-Null
+ Set-Content -LiteralPath (Join-Path $workItem 'app.apk') -Value 'apk'
+ Set-Content -LiteralPath (Join-Path $platformTools 'adb.exe') -Value 'adb'
+ [pscustomobject] @{
+ name = 'sample'
+ displayName = 'Sample APK Tests'
+ packageName = 'example.tests'
+ instrumentation = 'example.tests.TestInstrumentation'
+ } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $workItem 'case.json')
+
+ $propsPath = Join-Path $root 'items.props'
+ & (Join-Path $PSScriptRoot 'prepare-apk-test-helix-submission.ps1') `
+ -WorkItemsDirectory (Join-Path $root 'work-items') `
+ -ItemsPropsPath $propsPath `
+ -ResultsDirectory (Join-Path $root 'results') `
+ -PlatformToolsDirectory $platformTools `
+ -TargetMinutes 15
+
+ [xml] $props = Get-Content -LiteralPath $propsPath -Raw
+ $item = $props.Project.ItemGroup._ApkTestHelixWorkItem
+ Assert-Equal 'apk-tests-sample' ([string] $item.Include) 'Work item name should be deterministic.'
+ Assert-Equal 'results.trx;console.log;logcat.log;device-state.log;case.json;work-item-error.log' ([string] $item.DownloadFilesFromResults) 'Result files should be downloaded.'
+
+ $script = Get-Content -LiteralPath (Join-Path $workItem 'run-apk-tests.ps1') -Raw
+ if (-not $script.Contains('$packageName = ''example.tests''') -or
+ -not $script.Contains('$instrumentation = ''example.tests.TestInstrumentation''') -or
+ -not $script.Contains('INSTRUMENTATION_RESULT: resultsPath=')) {
+ throw 'Generated APK test script is missing required instrumentation values.'
+ }
+ if (-not (Test-Path -LiteralPath (Join-Path $workItem 'platform-tools\adb.exe') -PathType Leaf)) {
+ throw 'Platform tools were not copied into the work item payload.'
+ }
+ $tokens = $null
+ $errors = $null
+ [Management.Automation.Language.Parser]::ParseFile((Join-Path $workItem 'run-apk-tests.ps1'), [ref] $tokens, [ref] $errors) | Out-Null
+ if ($errors.Count -gt 0) {
+ throw ($errors | ForEach-Object Message | Out-String)
+ }
+
+ Write-Host 'ApkTestsHelix tests passed.'
+} finally {
+ Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction Ignore
+}
diff --git a/build-tools/automation/apk-tests-helix/README.md b/build-tools/automation/apk-tests-helix/README.md
new file mode 100644
index 00000000000..a6a72ce8f2c
--- /dev/null
+++ b/build-tools/automation/apk-tests-helix/README.md
@@ -0,0 +1,18 @@
+# APK tests in Helix prototype
+
+This guarded prototype builds the existing on-device APK test flavors once on a
+Windows Azure Pipelines agent, then submits one deterministic Helix work item per flavor. Existing
+measurements put every device test invocation below the configurable 15-minute target,
+so splitting an APK's NUnit inventory further would add installation overhead without
+improving the tail.
+
+Each small work item contains a signed APK, Android `platform-tools`, its
+package/instrumentation metadata, and a PowerShell runner, matching the proven
+PR #12020 payload layout.
+The runner installs the APK, invokes `am instrument`, pulls the test-generated TRX,
+captures logcat/device state, and returns failure when instrumentation or any test
+fails.
+
+The prototype uses `windows.11.amd64.android.open`, following the MAUI R2R Android
+Helix implementation. The existing macOS emulator lanes remain enabled for same-build
+inventory and outcome comparison.
diff --git a/build-tools/automation/apk-tests-helix/prepare-apk-test-helix-submission.ps1 b/build-tools/automation/apk-tests-helix/prepare-apk-test-helix-submission.ps1
new file mode 100644
index 00000000000..35be92618a1
--- /dev/null
+++ b/build-tools/automation/apk-tests-helix/prepare-apk-test-helix-submission.ps1
@@ -0,0 +1,201 @@
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory)]
+ [string] $WorkItemsDirectory,
+
+ [Parameter(Mandatory)]
+ [string] $ItemsPropsPath,
+
+ [Parameter(Mandatory)]
+ [string] $ResultsDirectory,
+
+ [Parameter(Mandatory)]
+ [string] $PlatformToolsDirectory,
+
+ [ValidateRange(1, 1440)]
+ [int] $TargetMinutes = 15
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+function Copy-PayloadDirectory
+{
+ param (
+ [Parameter(Mandatory)]
+ [string] $Source,
+
+ [Parameter(Mandatory)]
+ [string] $Destination
+ )
+
+ New-Item -ItemType Directory -Force -Path $Destination | Out-Null
+ if ($IsWindows) {
+ & robocopy $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:1 /NFL /NDL /NJH /NJS /NP
+ if ($LASTEXITCODE -gt 7) {
+ throw "robocopy failed with exit code $LASTEXITCODE while copying '$Source'."
+ }
+ $global:LASTEXITCODE = 0
+ } else {
+ & cp -a "$Source/." "$Destination/"
+ if ($LASTEXITCODE -ne 0) {
+ throw "cp failed with exit code $LASTEXITCODE while copying '$Source'."
+ }
+ }
+}
+
+function Escape-PowerShellSingleQuotedString
+{
+ param (
+ [Parameter(Mandatory)]
+ [string] $Value
+ )
+
+ return $Value.Replace("'", "''")
+}
+
+$workItemsFullPath = [IO.Path]::GetFullPath($WorkItemsDirectory)
+$itemsPropsFullPath = [IO.Path]::GetFullPath($ItemsPropsPath)
+$resultsFullPath = [IO.Path]::GetFullPath($ResultsDirectory)
+
+if (-not (Test-Path -LiteralPath $workItemsFullPath -PathType Container)) {
+ throw "Work item directory '$workItemsFullPath' does not exist."
+}
+if (-not (Test-Path -LiteralPath $PlatformToolsDirectory -PathType Container)) {
+ throw "Android platform-tools directory '$PlatformToolsDirectory' does not exist."
+}
+
+New-Item -ItemType Directory -Force -Path $resultsFullPath | Out-Null
+
+$cases = [System.Collections.Generic.List[object]]::new()
+foreach ($caseFile in Get-ChildItem -LiteralPath $workItemsFullPath -Filter 'case.json' -Recurse | Sort-Object FullName) {
+ $case = Get-Content -LiteralPath $caseFile.FullName -Raw | ConvertFrom-Json
+ $payloadDirectory = Split-Path -Parent $caseFile.FullName
+ $apkPath = Join-Path $payloadDirectory 'app.apk'
+ if (-not (Test-Path -LiteralPath $apkPath -PathType Leaf)) {
+ throw "APK payload '$apkPath' does not exist."
+ }
+ Copy-PayloadDirectory -Source $PlatformToolsDirectory -Destination (Join-Path $payloadDirectory 'platform-tools')
+
+ $packageName = Escape-PowerShellSingleQuotedString ([string] $case.packageName)
+ $instrumentation = Escape-PowerShellSingleQuotedString ([string] $case.instrumentation)
+ $script = @'
+$ErrorActionPreference = 'Stop'
+$upload = $env:HELIX_WORKITEM_UPLOAD_ROOT
+$adb = Join-Path $PSScriptRoot 'platform-tools\adb.exe'
+$apk = Join-Path $PSScriptRoot 'app.apk'
+$packageName = '__PACKAGE_NAME__'
+$instrumentation = '__INSTRUMENTATION__'
+$exitCode = 1
+
+if ([string]::IsNullOrWhiteSpace($upload)) {
+ throw 'HELIX_WORKITEM_UPLOAD_ROOT is not set.'
+}
+New-Item -ItemType Directory -Force -Path $upload | Out-Null
+Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'case.json') -Destination (Join-Path $upload 'case.json')
+
+try {
+ & $adb devices -l | Tee-Object -FilePath (Join-Path $upload 'adb-devices.log')
+ if ($LASTEXITCODE -ne 0) {
+ throw "adb devices failed with exit code $LASTEXITCODE."
+ }
+
+ & $adb uninstall $packageName *> $null
+ & $adb install -r $apk
+ if ($LASTEXITCODE -ne 0) {
+ throw "adb install failed with exit code $LASTEXITCODE."
+ }
+
+ & $adb logcat -c
+ $instrumentationOutput = @(& $adb shell am instrument -w -r "$packageName/$instrumentation" 2>&1)
+ $instrumentationOutput | Tee-Object -FilePath (Join-Path $upload 'console.log')
+ if ($LASTEXITCODE -ne 0) {
+ throw "adb instrument failed with exit code $LASTEXITCODE."
+ }
+
+ $resultPathMatch = [regex]::Match(($instrumentationOutput -join "`n"), '(?m)^INSTRUMENTATION_RESULT: resultsPath=(?.+)$')
+ if (-not $resultPathMatch.Success) {
+ throw 'Instrumentation did not report a TRX result path.'
+ }
+
+ $deviceResultsPath = $resultPathMatch.Groups['path'].Value.Trim()
+ $localResultsPath = Join-Path $upload 'results.trx'
+ & $adb pull $deviceResultsPath $localResultsPath
+ if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $localResultsPath -PathType Leaf)) {
+ throw "Failed to pull TRX from '$deviceResultsPath'."
+ }
+
+ [xml] $trx = Get-Content -LiteralPath $localResultsPath -Raw
+ $failed = @($trx.GetElementsByTagName('UnitTestResult') | Where-Object { $_.GetAttribute('outcome') -eq 'Failed' })
+ if ($failed.Count -gt 0) {
+ throw "$($failed.Count) on-device test(s) failed."
+ }
+
+ $exitCode = 0
+} catch {
+ "ERROR: $($_.Exception.Message)" | Tee-Object -FilePath (Join-Path $upload 'work-item-error.log') -Append
+} finally {
+ @(
+ '===== get-state ====='
+ (& $adb get-state 2>&1)
+ '===== boot completion ====='
+ (& $adb shell getprop sys.boot_completed 2>&1)
+ '===== disk ====='
+ (& $adb shell df /data 2>&1)
+ '===== packages ====='
+ (& $adb shell pm list packages -3 2>&1)
+ ) | Set-Content -LiteralPath (Join-Path $upload 'device-state.log')
+ & $adb logcat -d -b all *> (Join-Path $upload 'logcat.log')
+ & $adb uninstall $packageName *> $null
+}
+
+exit $exitCode
+'@
+ $script = $script.Replace('__PACKAGE_NAME__', $packageName).Replace('__INSTRUMENTATION__', $instrumentation)
+ [IO.File]::WriteAllText((Join-Path $payloadDirectory 'run-apk-tests.ps1'), $script, [Text.UTF8Encoding]::new($false))
+
+ $cases.Add([pscustomobject] @{
+ Name = [string] $case.name
+ DisplayName = [string] $case.displayName
+ PayloadDirectory = $payloadDirectory
+ })
+}
+
+if ($cases.Count -eq 0) {
+ throw "No case.json files were found under '$workItemsFullPath'."
+}
+
+$settings = [Xml.XmlWriterSettings]::new()
+$settings.Indent = $true
+$settings.Encoding = [Text.UTF8Encoding]::new($false)
+$writer = [Xml.XmlWriter]::Create($itemsPropsFullPath, $settings)
+try {
+ $writer.WriteStartElement('Project')
+ $writer.WriteStartElement('ItemGroup')
+ foreach ($case in $cases) {
+ $writer.WriteStartElement('_ApkTestHelixWorkItem')
+ $writer.WriteAttributeString('Include', "apk-tests-$($case.Name)")
+ $writer.WriteElementString('PayloadDirectory', $case.PayloadDirectory)
+ $writer.WriteElementString('Command', 'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File run-apk-tests.ps1')
+ $writer.WriteElementString('DownloadFilesFromResults', 'results.trx;console.log;logcat.log;device-state.log;case.json;work-item-error.log')
+ $writer.WriteEndElement()
+ }
+ $writer.WriteEndElement()
+ $writer.WriteEndElement()
+} finally {
+ $writer.Dispose()
+}
+
+[pscustomobject] @{
+ targetMinutes = $TargetMinutes
+ workItemCount = $cases.Count
+ cases = $cases | ForEach-Object {
+ [pscustomobject] @{
+ name = $_.Name
+ displayName = $_.DisplayName
+ }
+ }
+} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $resultsFullPath 'work-item-generation.json') -Encoding utf8NoBOM
+
+Write-Host "Prepared $($cases.Count) APK test Helix work items."
+Write-Host "##vso[task.setvariable variable=ApkTestsHelixItemsProps]$itemsPropsFullPath"
diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml
index eb12c44be1b..648191f6e01 100644
--- a/build-tools/automation/azure-pipelines-public.yaml
+++ b/build-tools/automation/azure-pipelines-public.yaml
@@ -29,6 +29,18 @@ parameters:
values:
- 'true'
- 'false'
+- name: enableApkTestsHelixPrototype
+ displayName: Enable APK tests in Helix prototype
+ type: boolean
+ default: false
+- name: apkTestsHelixTargetMinutes
+ displayName: APK test Helix target minutes
+ type: number
+ default: 15
+- name: apkTestsHelixOnly
+ displayName: Run only the APK test Helix prototype after builds
+ type: boolean
+ default: false
# Repository resources
resources:
@@ -157,6 +169,27 @@ stages:
nugetArtifactName: $(LinuxNuGetArtifactName)
use1ESTemplate: false
+- ${{ if eq(parameters.enableApkTestsHelixPrototype, true) }}:
+ - stage: apk_tests_helix
+ displayName: APK Tests Helix Prototype
+ dependsOn:
+ - mac_build
+ condition: and(succeeded(), or(ne(variables['SkipTestStages'], 'true'), eq('${{ parameters.apkTestsHelixOnly }}', 'true')))
+ jobs:
+ - job: apk_tests_helix
+ displayName: Android APK Tests > Helix
+ pool:
+ name: $(NetCorePublicPoolName)
+ demands:
+ - ImageOverride -equals $(WindowsPoolImageNetCorePublic)
+ timeoutInMinutes: 240
+ workspace:
+ clean: all
+ steps:
+ - template: /build-tools/automation/yaml-templates/run-apk-tests-in-helix.yaml
+ parameters:
+ targetMinutes: ${{ parameters.apkTestsHelixTargetMinutes }}
+
# Package Tests Stage
- ${{ if ne(variables.SkipTestStages, 'true') }}:
- template: /build-tools/automation/yaml-templates/stage-package-tests.yaml
diff --git a/build-tools/automation/yaml-templates/run-apk-tests-in-helix.yaml b/build-tools/automation/yaml-templates/run-apk-tests-in-helix.yaml
new file mode 100644
index 00000000000..cdcf9ee4bc4
--- /dev/null
+++ b/build-tools/automation/yaml-templates/run-apk-tests-in-helix.yaml
@@ -0,0 +1,227 @@
+parameters:
+ targetMinutes: 15
+ workItemTimeout: '00:30:00'
+ helixQueue: windows.11.amd64.android.open
+ testCases:
+ - name: debug
+ displayName: Mono.Android.NET_Tests-Debug
+ configuration: Debug
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: ''
+ - name: release
+ displayName: Mono.Android.NET_Tests-Release
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: ''
+ - name: no-aab
+ displayName: Mono.Android.NET_Tests-NoAab
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: '-p:TestsFlavor=NoAab'
+ - name: coreclr
+ displayName: Mono.Android.NET_Tests-CoreCLR
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: '-p:TestsFlavor=CoreCLR -p:UseMonoRuntime=false'
+ - name: mono
+ displayName: Mono.Android.NET_Tests-Mono
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: '-p:UseMonoRuntime=true -p:_DisableCheckForUnsupportedMonoMobileRuntime=true'
+ - name: coreclr-trimmable
+ displayName: Mono.Android.NET_Tests-CoreCLRTrimmable
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: '-p:TestsFlavor=CoreCLRTrimmable -p:AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false'
+ - name: nativeaot
+ displayName: Mono.Android.NET_Tests-NativeAOT
+ configuration: $(XA.Build.Configuration)
+ project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
+ artifactName: Mono.Android.NET_Tests-Signed.apk
+ packageName: Xamarin.Android.RuntimeTests
+ instrumentation: xamarin.android.runtimetests.TestInstrumentation
+ extraBuildArguments: '-p:TestsFlavor=NativeAOT -p:PublishAot=true'
+ - name: jcwgen
+ displayName: Xamarin.Android.JcwGen_Tests
+ configuration: $(XA.Build.Configuration)
+ project: tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj
+ artifactName: Xamarin.Android.JcwGen_Tests-Signed.apk
+ packageName: Xamarin.Android.JcwGen_Tests
+ instrumentation: xamarin.android.jcwgentests.TestInstrumentation
+ extraBuildArguments: ''
+
+steps:
+- template: /build-tools/automation/yaml-templates/setup-test-environment-public.yaml
+ parameters:
+ useAgentJdkPath: false
+ use1ESTemplate: false
+
+- pwsh: |
+ $ErrorActionPreference = 'Stop'
+ $androidSdk = @(
+ $env:ANDROID_HOME
+ $env:ANDROID_SDK_ROOT
+ $env:AndroidSdkDirectory
+ $(if ($env:USERPROFILE) { Join-Path $env:USERPROFILE 'android-toolchain\sdk' })
+ $(if ($env:HOME) { Join-Path $env:HOME 'android-toolchain/sdk' })
+ ) | Where-Object { $_ -and (Test-Path (Join-Path $_ 'platform-tools')) } | Select-Object -First 1
+ if (-not $androidSdk) {
+ throw 'Could not locate the prepared Android SDK.'
+ }
+ New-Item -ItemType Directory -Force -Path '$(Build.StagingDirectory)/apk-tests-helix/results' | Out-Null
+ Write-Host "##vso[task.setvariable variable=ApkTestsAndroidSdkDirectory]$androidSdk"
+ displayName: Resolve APK test build paths
+
+- task: DownloadPipelineArtifact@2
+ inputs:
+ artifactName: $(TestAssembliesArtifactName)
+ downloadPath: $(System.DefaultWorkingDirectory)/bin/Test$(XA.Build.Configuration)
+
+- task: DotNetCoreCLI@2
+ displayName: Build BootstrapTasks Debug
+ inputs:
+ projects: $(System.DefaultWorkingDirectory)/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj
+ arguments: -c Debug -bl:$(Build.StagingDirectory)/apk-tests-helix/BootstrapTasks-Debug.binlog
+
+- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml
+ parameters:
+ project: Xamarin.Android.slnx
+ arguments: -t:PrepareJavaInterop -c Debug --no-restore
+ displayName: Prepare Java.Interop Debug
+ continueOnError: false
+
+- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml
+ parameters:
+ project: Xamarin.Android.slnx
+ arguments: -t:PrepareJavaInterop -c $(XA.Build.Configuration) --no-restore
+ displayName: Prepare Java.Interop $(XA.Build.Configuration)
+ continueOnError: false
+
+- ${{ each testCase in parameters.testCases }}:
+ - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml
+ parameters:
+ command: build
+ project: ${{ testCase.project }}
+ arguments: >-
+ -t:Clean
+ -c ${{ testCase.configuration }}
+ -p:AndroidSdkDirectory="$(ApkTestsAndroidSdkDirectory)"
+ -p:JavaSdkDirectory="$(JAVA_HOME)"
+ ${{ testCase.extraBuildArguments }}
+ displayName: Clean APK payload - ${{ testCase.displayName }}
+ continueOnError: false
+
+ - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml
+ parameters:
+ command: build
+ project: ${{ testCase.project }}
+ arguments: >-
+ -t:SignAndroidPackage
+ -c ${{ testCase.configuration }}
+ -p:AndroidPackageFormat=apk
+ -p:AndroidSdkDirectory="$(ApkTestsAndroidSdkDirectory)"
+ -p:JavaSdkDirectory="$(JAVA_HOME)"
+ ${{ testCase.extraBuildArguments }}
+ -bl:$(Build.StagingDirectory)/apk-tests-helix/build-${{ testCase.name }}.binlog
+ displayName: Build APK payload - ${{ testCase.displayName }}
+ continueOnError: false
+
+ - pwsh: |
+ $ErrorActionPreference = 'Stop'
+ $payloadDirectory = '$(Build.StagingDirectory)/apk-tests-helix/work-items/${{ testCase.name }}'
+ New-Item -ItemType Directory -Force -Path $payloadDirectory | Out-Null
+ $apk = Get-ChildItem '$(System.DefaultWorkingDirectory)/bin/Test${{ testCase.configuration }}' -Recurse -Filter '${{ testCase.artifactName }}' |
+ Sort-Object LastWriteTime -Descending |
+ Select-Object -First 1
+ if (-not $apk) {
+ throw "Could not find ${{ testCase.artifactName }} for ${{ testCase.displayName }}."
+ }
+ Copy-Item -LiteralPath $apk.FullName -Destination (Join-Path $payloadDirectory 'app.apk')
+ [pscustomobject]@{
+ name = '${{ testCase.name }}'
+ displayName = '${{ testCase.displayName }}'
+ packageName = '${{ testCase.packageName }}'
+ instrumentation = '${{ testCase.instrumentation }}'
+ } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $payloadDirectory 'case.json') -Encoding utf8NoBOM
+ displayName: Stage APK payload - ${{ testCase.displayName }}
+
+ - pwsh: |
+ $dotnet = '$(System.DefaultWorkingDirectory)\bin\$(XA.Build.Configuration)\dotnet\dotnet.exe'
+ & $dotnet build-server shutdown
+ exit $LASTEXITCODE
+ displayName: Shut down build servers - ${{ testCase.displayName }}
+
+- pwsh: |
+ $ErrorActionPreference = 'Stop'
+ $platformTools = Join-Path '$(ApkTestsAndroidSdkDirectory)' 'platform-tools'
+ if (-not (Test-Path -LiteralPath $platformTools -PathType Container)) {
+ throw 'Could not locate Android platform-tools.'
+ }
+ & '$(System.DefaultWorkingDirectory)/build-tools/automation/apk-tests-helix/prepare-apk-test-helix-submission.ps1' `
+ -WorkItemsDirectory '$(Build.StagingDirectory)/apk-tests-helix/work-items' `
+ -ItemsPropsPath '$(Build.StagingDirectory)/apk-tests-helix/work-items.props' `
+ -ResultsDirectory '$(Build.StagingDirectory)/apk-tests-helix/results' `
+ -PlatformToolsDirectory $platformTools `
+ -TargetMinutes ${{ parameters.targetMinutes }}
+ displayName: Prepare APK test Helix submission
+
+- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml
+ parameters:
+ command: msbuild
+ project: $(System.DefaultWorkingDirectory)/build-tools/automation/ApkTestsHelix.proj
+ arguments: >-
+ /restore
+ /t:Test
+ /p:TreatWarningsAsErrors=false
+ /bl:$(Build.StagingDirectory)/apk-tests-helix/results/send-to-helix.binlog
+ displayName: Submit APK tests to Helix
+ continueOnError: false
+ taskTimeoutInMinutes: 180
+ env:
+ ApkTestsHelixItemsProps: $(ApkTestsHelixItemsProps)
+ ApkTestsHelixWorkItemTimeout: ${{ parameters.workItemTimeout }}
+ HelixResultsDestinationDir: $(Build.StagingDirectory)/apk-tests-helix/results
+ HelixSource: pr/dotnet/android
+ HelixType: tests/android/apk-tests/
+ HelixBuild: $(Build.BuildNumber)
+ HelixConfiguration: apk-tests
+ HelixTargetQueueForItem: ${{ parameters.helixQueue }}
+ HelixTestRunName: Android APK Tests - Helix Prototype
+ HelixAccessToken: ''
+ WaitForWorkItemCompletion: 'true'
+ Creator: dotnet-android
+
+- task: PublishTestResults@2
+ displayName: Publish APK Helix TRX results
+ condition: always()
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '**/*.trx'
+ searchFolder: $(Build.StagingDirectory)/apk-tests-helix/results
+ mergeTestResults: false
+ testRunTitle: Android APK Tests - Helix Prototype
+
+- task: PublishPipelineArtifact@1
+ displayName: Publish APK Helix diagnostics
+ condition: always()
+ inputs:
+ artifactName: APK Test Helix Results
+ targetPath: $(Build.StagingDirectory)/apk-tests-helix/results