Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ stages:
-ci
-restore
-test
-projects $(Build.SourcesDirectory)\tests\UnitTests.XHarness.Android.WindowsQueues.proj
/bl:$(Build.SourcesDirectory)\artifacts\log\$(_BuildConfig)\Helix.XHarness.Android.WindowsQueues.binlog
-projects $(Build.SourcesDirectory)\tests\UnitTests.XHarness.Android.Device.proj
/bl:$(Build.SourcesDirectory)\artifacts\log\$(_BuildConfig)\Helix.XHarness.Android.Device.binlog
/p:RestoreUsingNuGetTargets=false
/p:XharnessTestARM64_V8A=true
/p:XHarnessTestARM64_V8A=true
displayName: XHarness Android Helix Testing (Windows)
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Expand Down Expand Up @@ -210,11 +210,11 @@ stages:
-ci
-restore
-test
-projects $(Build.SourcesDirectory)/tests/UnitTests.XHarness.Android.LinuxQueues.proj
/bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/Helix.XHarness.Android.LinuxQueues.binlog
-projects $(Build.SourcesDirectory)/tests/UnitTests.XHarness.Android.Simulator.proj
/bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/Helix.XHarness.Android.Simulator.binlog
/p:RestoreUsingNuGetTargets=false
/p:XharnessTestX86=true
/p:XharnessTestX86_64=true
/p:XHarnessTestX86=true
/p:XHarnessTestX86_64=true
displayName: XHarness Android Helix Testing (Linux)
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ public void AndroidXHarnessWorkItemIsCreated()
_fileSystem.FileExists(payloadArchive).Should().BeTrue();

var command = workItem.GetMetadata("Command");
command.Should().Contain("--timeout \"00:08:55\"");
command.Should().Contain("-timeout \"00:08:55\"");

_zipArchiveManager
.Verify(x => x.AddResourceFileToArchive<CreateXHarnessAndroidWorkItems>(payloadArchive, It.Is<string>(s => s.Contains("xharness-helix-job.android.sh")), "xharness-helix-job.android.sh"), Times.AtLeastOnce);
_zipArchiveManager
.Verify(x => x.AddResourceFileToArchive<CreateXHarnessAndroidWorkItems>(payloadArchive, It.Is<string>(s => s.Contains("xharness-helix-job.android.bat")), "xharness-helix-job.android.bat"), Times.AtLeastOnce);
.Verify(x => x.AddResourceFileToArchive<CreateXHarnessAndroidWorkItems>(payloadArchive, It.Is<string>(s => s.Contains("xharness-helix-job.android.ps1")), "xharness-helix-job.android.ps1"), Times.AtLeastOnce);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void ArchivePayloadIsOverwritten()
CreateAppBundle("apps/System.Bar.app", "ios-simulator-64_13.5"),
};

_fileSystem.Files.Add("apps/xharness-app-payload-system.foo.app.zip", "archive");
_fileSystem.Files.Add("apps/xharness-app-payload-system.foo.zip", "archive");

// Act
using var provider = collection.BuildServiceProvider();
Expand Down
165 changes: 103 additions & 62 deletions src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAndroidWorkItems.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -15,13 +14,17 @@ namespace Microsoft.DotNet.Helix.Sdk
/// </summary>
public class CreateXHarnessAndroidWorkItems : XHarnessTaskBase
{
private const string ArgumentsPropName = "Arguments";
private const string AndroidInstrumentationNamePropName = "AndroidInstrumentationName";
private const string DeviceOutputPathPropName = "DeviceOutputPath";
private const string AndroidPackageNamePropName = "AndroidPackageName";
public static class MetadataNames
{
public const string Arguments = "Arguments";
public const string AndroidInstrumentationName = "AndroidInstrumentationName";
public const string DeviceOutputPath = "DeviceOutputPath";
public const string AndroidPackageName = "AndroidPackageName";
public const string ApkPath = "ApkPath";
}

private const string PosixAndroidWrapperScript = "xharness-helix-job.android.sh";
private const string NonPosixAndroidWrapperScript = "xharness-helix-job.android.bat";
private const string NonPosixAndroidWrapperScript = "xharness-helix-job.android.ps1";

/// <summary>
/// Boolean true if this is a posix shell, false if not.
Expand Down Expand Up @@ -64,34 +67,111 @@ public bool ExecuteTask(IZipArchiveManager zipArchiveManager, IFileSystem fileSy
/// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns>
private async Task<ITaskItem> PrepareWorkItem(IZipArchiveManager zipArchiveManager, IFileSystem fileSystem, ITaskItem appPackage)
{
string workItemName = fileSystem.GetFileNameWithoutExtension(appPackage.ItemSpec);

var (testTimeout, workItemTimeout, expectedExitCode, customCommands) = ParseMetadata(appPackage);
// The user can re-use the same .apk for 2 work items so the name of the work item will come from ItemSpec and path from metadata
Comment thread
premun marked this conversation as resolved.
string workItemName;
string apkPath;
if (appPackage.TryGetMetadata(MetadataNames.ApkPath, out string apkPathMetadata) && !string.IsNullOrEmpty(apkPathMetadata))
{
workItemName = appPackage.ItemSpec;
apkPath = apkPathMetadata;
}
else
{
workItemName = fileSystem.GetFileNameWithoutExtension(appPackage.ItemSpec);
Comment thread
MattGal marked this conversation as resolved.
apkPath = appPackage.ItemSpec;
}

string command = ValidateMetadataAndGetXHarnessAndroidCommand(appPackage, testTimeout, expectedExitCode);
if (!fileSystem.FileExists(apkPath))
{
Log.LogError($"App package not found in {apkPath}");
return null;
}

if (!fileSystem.GetExtension(appPackage.ItemSpec).Equals(".apk", StringComparison.OrdinalIgnoreCase))
if (!fileSystem.GetExtension(apkPath).Equals(".apk", StringComparison.OrdinalIgnoreCase))
{
Log.LogError($"Unsupported app package type: {fileSystem.GetFileName(appPackage.ItemSpec)}");
Log.LogError($"Unsupported app package type: {fileSystem.GetFileName(apkPath)}");
return null;
}

Log.LogMessage($"Creating work item with properties Identity: {workItemName}, Payload: {appPackage.ItemSpec}, Command: {command}");
// Validation of any metadata specific to Android stuff goes here
if (!appPackage.GetRequiredMetadata(Log, MetadataNames.AndroidPackageName, out string androidPackageName))
{
Log.LogError($"{MetadataNames.AndroidPackageName} metadata must be specified; this may match, but can vary from file name");
return null;
}

string workItemZip = await CreateZipArchiveOfPackageAsync(zipArchiveManager, fileSystem, appPackage.ItemSpec);
var (testTimeout, workItemTimeout, expectedExitCode, customCommands) = ParseMetadata(appPackage);

return new Build.Utilities.TaskItem(workItemName, new Dictionary<string, string>()
if (customCommands == null)
Comment thread
premun marked this conversation as resolved.
{
{ "Identity", workItemName },
{ "PayloadArchive", workItemZip },
{ "Command", command },
{ "Timeout", workItemTimeout.ToString() },
});
appPackage.TryGetMetadata(MetadataNames.Arguments, out string extraArguments);

var exitCodeArg = expectedExitCode != 0 ? $" --expected-exit-code $expected_exit_code " : string.Empty;
var passthroughArgs = !string.IsNullOrEmpty(AppArguments) ? $" -- {AppArguments}" : string.Empty;

var instrumentationArg = appPackage.TryGetMetadata(MetadataNames.AndroidInstrumentationName, out string androidInstrumentationName)
? $"--instrumentation \"{androidInstrumentationName}\" "
: string.Empty;

var devOutArg = appPackage.TryGetMetadata(MetadataNames.DeviceOutputPath, out string deviceOutputPath)
? $"--dev-out \"{deviceOutputPath}\" "
: string.Empty;

// In case user didn't specify custom commands, we use our default one
customCommands = "xharness android test " +
"--app \"$app\" " +
"--output-directory \"$output_directory\" " +
"--timeout \"$timeout\" " +
"--package-name \"$package_name\" " +
" -v "
+ devOutArg
+ instrumentationArg
+ exitCodeArg
+ extraArguments
+ passthroughArgs;
}

string command = GetHelixCommand(appPackage, apkPath, androidPackageName, testTimeout, expectedExitCode);

Log.LogMessage($"Creating work item with properties Identity: {workItemName}, Payload: {apkPath}, Command: {command}");

string workItemZip = await CreateZipArchiveOfPackageAsync(zipArchiveManager, fileSystem, workItemName, apkPath, customCommands);

return CreateTaskItem(workItemName, workItemZip, command, workItemTimeout);
}

private string GetHelixCommand(ITaskItem appPackage, string apkPath, string androidPackageName, TimeSpan xHarnessTimeout, int expectedExitCode)
{
appPackage.TryGetMetadata(MetadataNames.AndroidInstrumentationName, out string androidInstrumentationName);
appPackage.TryGetMetadata(MetadataNames.DeviceOutputPath, out string deviceOutputPath);

string wrapperScriptName = IsPosixShell ? PosixAndroidWrapperScript : NonPosixAndroidWrapperScript;
string xharnessHelixWrapperScript = IsPosixShell ? $"chmod +x ./{wrapperScriptName} && ./{wrapperScriptName}"
: $"powershell -ExecutionPolicy ByPass -NoProfile -File \"{wrapperScriptName}\"";

// We either call .ps1 or .sh so we need to format the arguments well (PS has -argument, bash has --argument)
string dash = IsPosixShell ? "--" : "-";
string xharnessRunCommand = $"{xharnessHelixWrapperScript} " +
$"{dash}app \"{Path.GetFileName(apkPath)}\" " +
$"{dash}timeout \"{xHarnessTimeout}\" " +
$"{dash}package_name \"{androidPackageName}\" " +
(expectedExitCode != 0 ? $" {dash}expected_exit_code \"{expectedExitCode}\" " : string.Empty) +
(string.IsNullOrEmpty(deviceOutputPath) ? string.Empty : $"{dash}device_output_path \"{deviceOutputPath}\" ") +
(string.IsNullOrEmpty(androidInstrumentationName) ? string.Empty : $"{dash}instrumentation \"{androidInstrumentationName}\" ");

Log.LogMessage(MessageImportance.Low, $"Generated XHarness command: {xharnessRunCommand}");

return xharnessRunCommand;
}

private async Task<string> CreateZipArchiveOfPackageAsync(IZipArchiveManager zipArchiveManager, IFileSystem fileSystem, string fileToZip)
private async Task<string> CreateZipArchiveOfPackageAsync(
IZipArchiveManager zipArchiveManager,
IFileSystem fileSystem,
string workItemName,
string fileToZip,
string injectedCommands)
{
string fileName = $"xharness-apk-payload-{fileSystem.GetFileNameWithoutExtension(fileToZip).ToLowerInvariant()}.zip";
string fileName = $"xharness-apk-payload-{workItemName.ToLowerInvariant()}.zip";
Comment thread
premun marked this conversation as resolved.
string outputZipPath = fileSystem.PathCombine(fileSystem.GetDirectoryName(fileToZip), fileName);

if (fileSystem.FileExists(outputZipPath))
Expand All @@ -106,48 +186,9 @@ private async Task<string> CreateZipArchiveOfPackageAsync(IZipArchiveManager zip
// so we'll always include both scripts (very small)
await zipArchiveManager.AddResourceFileToArchive<CreateXHarnessAndroidWorkItems>(outputZipPath, ScriptNamespace + PosixAndroidWrapperScript, PosixAndroidWrapperScript);
await zipArchiveManager.AddResourceFileToArchive<CreateXHarnessAndroidWorkItems>(outputZipPath, ScriptNamespace + NonPosixAndroidWrapperScript, NonPosixAndroidWrapperScript);
await zipArchiveManager.AddContentToArchive(outputZipPath, CustomCommandsScript + (IsPosixShell ? ".sh" : ".ps1"), injectedCommands);

return outputZipPath;
}

private string ValidateMetadataAndGetXHarnessAndroidCommand(ITaskItem appPackage, TimeSpan xHarnessTimeout, int expectedExitCode)
{
// Validation of any metadata specific to Android stuff goes here
if (!appPackage.GetRequiredMetadata(Log, AndroidPackageNamePropName, out string androidPackageName))
{
Log.LogError($"{AndroidPackageNamePropName} metadata must be specified; this may match, but can vary from file name");
return null;
}

appPackage.TryGetMetadata(ArgumentsPropName, out string arguments);
appPackage.TryGetMetadata(AndroidInstrumentationNamePropName, out string androidInstrumentationName);
appPackage.TryGetMetadata(DeviceOutputPathPropName, out string deviceOutputPath);

string outputPathArg = string.IsNullOrEmpty(deviceOutputPath) ? string.Empty : $"--dev-out={deviceOutputPath} ";
string instrumentationArg = string.IsNullOrEmpty(androidInstrumentationName) ? string.Empty : $"-i={androidInstrumentationName} ";

string outputDirectory = IsPosixShell ? "$HELIX_WORKITEM_UPLOAD_ROOT" : "%HELIX_WORKITEM_UPLOAD_ROOT%";
string wrapperScriptName = IsPosixShell ? PosixAndroidWrapperScript : NonPosixAndroidWrapperScript;

string xharnessHelixWrapperScript = IsPosixShell ? $"chmod +x ./{wrapperScriptName} && ./{wrapperScriptName}"
: $"call {wrapperScriptName}";

string xharnessRunCommand = $"{xharnessHelixWrapperScript} " +
$"dotnet exec \"{(IsPosixShell ? "$XHARNESS_CLI_PATH" : "%XHARNESS_CLI_PATH%")}\" android test " +
$"--app \"{Path.GetFileName(appPackage.ItemSpec)}\" " +
$"--output-directory \"{outputDirectory}\" " +
$"--timeout \"{xHarnessTimeout}\" " +
$"-p=\"{androidPackageName}\" " +
"-v " +
(expectedExitCode != 0 ? $" --expected-exit-code \"{expectedExitCode}\" " : string.Empty) +
outputPathArg +
instrumentationArg +
arguments +
(!string.IsNullOrEmpty(AppArguments) ? $" -- {AppArguments}" : string.Empty);

Log.LogMessage(MessageImportance.Low, $"Generated XHarness command: {xharnessRunCommand}");

return xharnessRunCommand;
}
}
}
Loading