/// <summary> /// Prepares HelixWorkItem given xUnit project information. /// </summary> /// <param name="publishPath">The non-relative path to the publish directory.</param> /// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns> private async Task <ITaskItem> PrepareWorkItem(ITaskItem xunitProject) { // Forces this task to run asynchronously await Task.Yield(); if (!xunitProject.GetRequiredMetadata(Log, "PublishDirectory", out string publishDirectory)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "TargetPath", out string targetPath)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "RuntimeTargetFramework", out string runtimeTargetFramework)) { return(null); } xunitProject.TryGetMetadata("Arguments", out string arguments); string assemblyName = Path.GetFileName(targetPath); string driver = runtimeTargetFramework.Contains("core") ? $"{PathToDotnet} exec " : ""; string runnerName = runtimeTargetFramework.Contains("core") ? "xunit.console.dll" : "xunit.console.exe"; string correlationPayload = IsPosixShell ? "$HELIX_CORRELATION_PAYLOAD" : "%HELIX_CORRELATION_PAYLOAD%"; string xUnitRunner = $"{correlationPayload}/tools/{runtimeTargetFramework}/{runnerName}"; if (runtimeTargetFramework.Contains("core")) { var assemblyBaseName = assemblyName; if (assemblyBaseName.EndsWith(".dll")) { assemblyBaseName = assemblyBaseName.Substring(0, assemblyBaseName.Length - 4); } Log.LogMessage($"Adding runtimeconfig and depsfile parameters for assembly {assemblyBaseName}."); driver += $"--runtimeconfig {assemblyBaseName}.runtimeconfig.json --depsfile {assemblyBaseName}.deps.json "; } string command = $"{driver}{xUnitRunner} {assemblyName}{(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {arguments}"; Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); return(new Microsoft.Build.Utilities.TaskItem(assemblyName, new Dictionary <string, string>() { { "Identity", assemblyName }, { "PayloadDirectory", publishDirectory }, { "Command", command } })); }
private string ValidateMetadataAndGetXHarnessAndroidCommand(ITaskItem appPackage, TimeSpan xHarnessTimeout) { // Validation of any metadata specific to Android stuff goes here if (!appPackage.GetRequiredMetadata(Log, "AndroidPackageName", out string androidPackageName)) { Log.LogError("AndroidPackageName metadata must be specified; this may match, but can vary from file name"); return(null); } appPackage.TryGetMetadata("Arguments", out string arguments); appPackage.TryGetMetadata("AndroidInstrumentationName", out string androidInstrumentationName); appPackage.TryGetMetadata("DeviceOutputPath", 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 xharnessRunCommand = $"dotnet exec \"{(IsPosixShell ? "$XHARNESS_CLI_PATH" : "%XHARNESS_CLI_PATH%")}\" android test " + $"--app \"{Path.GetFileName(appPackage.ItemSpec)}\" " + $"--output-directory \"{outputDirectory}\" " + $"--timeout {xHarnessTimeout.TotalSeconds} " + $"-p=\"{androidPackageName}\" " + "-v " + outputPathArg + instrumentationArg + arguments + (!string.IsNullOrEmpty(AppArguments) ? $" -- {AppArguments}" : string.Empty); Log.LogMessage(MessageImportance.Low, $"Generated XHarness command: {xharnessRunCommand}"); return(xharnessRunCommand); }
private IJobDefinition AddProperty(IJobDefinition def, ITaskItem property) { if (!property.GetRequiredMetadata(Log, "Identity", out string key)) { return(def); } if (!property.GetRequiredMetadata(Log, "Value", out string value)) { return(def); } def.WithProperty(key, value); Log.LogMessage($"Added property '{key}' (value: '{value}') to job definition."); return(def); }
private string ValidateMetadataAndGetXHarnessiOSCommand(ITaskItem appFolderPath, TimeSpan xHarnessTimeout) { // Validation of any metadata specific to iOS stuff goes here if (!appFolderPath.GetRequiredMetadata(Log, "Targets", out string targets)) { Log.LogError("'Targets' metadata must be specified - " + "expecting list of target device/simulator platforms to execute tests on (e.g. ios-simulator-64)"); return(null); } // We need to call 'sudo launchctl' to spawn the process in a user session with GUI rendering capabilities string xharnessRunCommand = $"sudo launchctl asuser `id -u` sh \"{PayloadScriptName}\" " + $"--app \"$HELIX_WORKITEM_ROOT/{Path.GetFileName(appFolderPath.ItemSpec)}\" " + $"--output-directory \"$HELIX_WORKITEM_UPLOAD_ROOT\" " + $"--targets \"{targets}\" " + $"--timeout \"{xHarnessTimeout.TotalSeconds}\" " + $"--launch-timeout 600 " + $"--dotnet-root \"$DOTNET_ROOT\" " + $"--xharness \"$HELIX_CORRELATION_PAYLOAD/xharness-cli/xharness\" " + $"--xcode-version {XcodeVersion}" + (!string.IsNullOrEmpty(AppArguments) ? $" --app-arguments \"{AppArguments}\"" : string.Empty); Log.LogMessage(MessageImportance.Low, $"Generated XHarness command: {xharnessRunCommand}"); return(xharnessRunCommand); }
/// <summary> /// Prepares HelixWorkItem that can run on a device (currently Android or iOS) using XHarness /// </summary> /// <param name="appPackage">Path to application package</param> /// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns> private async Task <ITaskItem> PrepareWorkItem(IZipArchiveManager zipArchiveManager, IFileSystem fileSystem, ITaskItem 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 // This can be useful when we want to run the same app with different parameters or run the same app on different test targets, e.g. iOS 13 and 13.5 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); apkPath = appPackage.ItemSpec; } if (!fileSystem.FileExists(apkPath)) { Log.LogError($"App package not found in {apkPath}"); return(null); } if (!fileSystem.GetExtension(apkPath).Equals(".apk", StringComparison.OrdinalIgnoreCase)) { Log.LogError($"Unsupported app package type: {fileSystem.GetFileName(apkPath)}"); return(null); } // 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); } var(testTimeout, workItemTimeout, expectedExitCode, customCommands) = ParseMetadata(appPackage); if (customCommands == null) { // When no user commands are specified, we add the default `android test ...` command customCommands = GetDefaultCommand(appPackage, expectedExitCode); } 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 ValidateMetadataAndGetXHarnessIosCommand(ITaskItem appFolderPath, TimeSpan xHarnessTimeout) { // Validation of any metadata specific to iOS stuff goes here if (!appFolderPath.GetRequiredMetadata(Log, "Targets", out string targets)) { Log.LogError("'Targets' metadata must be specified; this may match, but can vary from file name"); return(null); } string workDirectory = "$HELIX_WORKITEM_ROOT"; string xharnessRunCommand = $"xharness ios test --app {workDirectory}/{Path.GetFileName(appFolderPath.ItemSpec)} +" + $" --output-directory=$HELIX_WORKITEM_UPLOAD_ROOT --targets={targets}" + $"--timeout={xHarnessTimeout.TotalSeconds} --working-directory={workDirectory} " + $"-v"; Log.LogMessage(MessageImportance.Low, $"Generated XHarness command: {xharnessRunCommand}"); return(xharnessRunCommand); }
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); }
private IJobDefinition AddWorkItem(IJobDefinition def, ITaskItem workItem) { if (!workItem.GetRequiredMetadata(Log, "Identity", out string name)) { return(def); } if (name.Contains('%')) { Log.LogWarning($"Work Item named '{name}' contains encoded characters which is not recommended."); } var cleanedName = Helpers.CleanWorkItemName(name); if (name != cleanedName) { Log.LogWarning($"Work Item named '{name}' contains unsupported characters and has been renamed to '{cleanedName}'."); } name = cleanedName; if (!workItem.GetRequiredMetadata(Log, "Command", out string command)) { return(def); } Log.LogMessage(MessageImportance.Low, $"Adding work item '{name}'"); var commands = GetCommands(workItem, command).ToList(); IWorkItemDefinitionWithPayload wiWithPayload; if (commands.Count == 1) { wiWithPayload = def.DefineWorkItem(name).WithCommand(commands[0]); Log.LogMessage(MessageImportance.Low, $" Command: '{commands[0]}'"); } else { string commandFile = _commandPayload.AddCommandFile(commands); string helixCorrelationPayload = IsPosixShell ? "$HELIX_CORRELATION_PAYLOAD/" : "%HELIX_CORRELATION_PAYLOAD%\\"; wiWithPayload = def.DefineWorkItem(name).WithCommand(helixCorrelationPayload + commandFile); Log.LogMessage(MessageImportance.Low, $" Command File: '{commandFile}'"); foreach (string c in commands) { Log.LogMessage(MessageImportance.Low, $" {c}"); } } string payloadDirectory = workItem.GetMetadata("PayloadDirectory"); string payloadArchive = workItem.GetMetadata("PayloadArchive"); string payloadUri = workItem.GetMetadata("PayloadUri"); IWorkItemDefinition wi; if (!string.IsNullOrEmpty(payloadUri)) { wi = wiWithPayload.WithPayloadUri(new Uri(payloadUri)); Log.LogMessage(MessageImportance.Low, $" Uri Payload: '{payloadUri}'"); } else if (!string.IsNullOrEmpty(payloadDirectory)) { wi = wiWithPayload.WithDirectoryPayload(payloadDirectory); Log.LogMessage(MessageImportance.Low, $" Directory Payload: '{payloadDirectory}'"); } else if (!string.IsNullOrEmpty(payloadArchive)) { wi = wiWithPayload.WithArchivePayload(payloadArchive); Log.LogMessage(MessageImportance.Low, $" Archive Payload: '{payloadArchive}'"); } else { wi = wiWithPayload.WithEmptyPayload(); Log.LogMessage(MessageImportance.Low, " Empty Payload"); } string timeoutString = workItem.GetMetadata("Timeout"); if (!string.IsNullOrEmpty(timeoutString)) { if (TimeSpan.TryParse(timeoutString, CultureInfo.InvariantCulture, out TimeSpan timeout)) { wi = wi.WithTimeout(timeout); Log.LogMessage(MessageImportance.Low, $" Timeout: '{timeout}'"); } else { Log.LogWarning($"Timeout value '{timeoutString}' could not be parsed."); } } else { Log.LogMessage(MessageImportance.Low, " Default Timeout"); } return(wi.AttachToJob()); }
/// <summary> /// Prepares HelixWorkItem given xUnit project information. /// </summary> /// <param name="publishPath">The non-relative path to the publish directory.</param> /// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns> private async Task <List <ITaskItem> > PrepareWorkItem(ITaskItem xunitProject) { // Forces this task to run asynchronously await Task.Yield(); if (!xunitProject.GetRequiredMetadata(Log, "PublishDirectory", out string publishDirectory)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "TargetPath", out string targetPath)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "RuntimeTargetFramework", out string runtimeTargetFramework)) { return(null); } xunitProject.TryGetMetadata("Arguments", out string arguments); string assemblyName = Path.GetFileName(targetPath); var runtimeTargetFrameworkParsed = NuGetFramework.Parse(runtimeTargetFramework); if (runtimeTargetFrameworkParsed.Framework != ".NETCoreApp") { throw new NotImplementedException("does not support non core runtime target"); } string driver = $"{PathToDotnet} exec "; // On mac due to https://github.com/dotnet/sdk/issues/3923, we run against workitem directory // but on Windows, if we running against working item diretory, we would hit long path. string testExecutionDirectory = IsPosixShell ? "-testExecutionDirectory $TestExecutionDirectory" : "-testExecutionDirectory %TestExecutionDirectory%"; string msbuildAdditionalSdkResolverFolder = IsPosixShell ? "" : "-msbuildAdditionalSdkResolverFolder %HELIX_CORRELATION_PAYLOAD%\\r"; var scheduler = new AssemblyScheduler(methodLimit: 40); var assemblyPartitionInfos = scheduler.Schedule(targetPath); var partitionedWorkItem = new List <ITaskItem>(); foreach (var assemblyPartitionInfo in assemblyPartitionInfos) { string command = $"{driver}{assemblyName} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} {(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {assemblyPartitionInfo.ClassListArgumentString} {arguments}"; Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); TimeSpan timeout = TimeSpan.FromMinutes(5); if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) { if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) { Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); } } partitionedWorkItem.Add(new Microsoft.Build.Utilities.TaskItem(assemblyPartitionInfo.DisplayName, new Dictionary <string, string>() { { "Identity", assemblyPartitionInfo.DisplayName }, { "PayloadDirectory", publishDirectory }, { "Command", command }, { "Timeout", timeout.ToString() }, })); } return(partitionedWorkItem); }
/// <summary> /// Prepares HelixWorkItem given xUnit project information. /// </summary> /// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns> private async Task <ITaskItem> PrepareWorkItem(ITaskItem xunitProject) { // Forces this task to run asynchronously await Task.Yield(); if (!xunitProject.GetRequiredMetadata(Log, "PublishDirectory", out string publishDirectory)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "TargetPath", out string targetPath)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "RuntimeTargetFramework", out string runtimeTargetFramework)) { return(null); } xunitProject.TryGetMetadata("Arguments", out string arguments); string assemblyName = Path.GetFileName(targetPath); string driver = runtimeTargetFramework.Contains("core") ? $"{PathToDotnet} exec " : ""; string runnerName = runtimeTargetFramework.Contains("core") ? "xunit.console.dll" : "xunit.console.exe"; string correlationPayload = IsPosixShell ? "$HELIX_CORRELATION_PAYLOAD" : "%HELIX_CORRELATION_PAYLOAD%"; string xUnitRunner = $"{correlationPayload}/xunit-runner/tools/{runtimeTargetFramework}/{runnerName}"; if (runtimeTargetFramework.Contains("core")) { // If we don't know what version we are, or it isn't 1.x or 2.x, add --roll-forward if (string.IsNullOrEmpty(DotNetCliVersion) || !(DotNetCliVersion.StartsWith("1.") || DotNetCliVersion.StartsWith("2."))) { Log.LogMessage("Adding dotnet cli roll-forward policy."); driver += "--roll-forward Major "; } var assemblyBaseName = assemblyName; if (assemblyBaseName.EndsWith(".dll")) { assemblyBaseName = assemblyBaseName.Substring(0, assemblyBaseName.Length - 4); } Log.LogMessage($"Adding runtimeconfig and depsfile parameters for assembly {assemblyBaseName}."); driver += $"--runtimeconfig {assemblyBaseName}.runtimeconfig.json --depsfile {assemblyBaseName}.deps.json "; } string command = $"{driver}{xUnitRunner} {assemblyName}{(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {arguments}"; Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); TimeSpan timeout = TimeSpan.FromMinutes(5); if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) { if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) { Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); } } var result = new Microsoft.Build.Utilities.TaskItem(assemblyName, new Dictionary <string, string>() { { "Identity", assemblyName }, { "PayloadDirectory", publishDirectory }, { "Command", command }, { "Timeout", timeout.ToString() }, }); xunitProject.CopyMetadataTo(result); return(result); }
/// <summary> /// Prepares HelixWorkItem given xUnit project information. /// </summary> /// <param name="publishPath">The non-relative path to the publish directory.</param> /// <returns>An ITaskItem instance representing the prepared HelixWorkItem.</returns> private async Task <List <ITaskItem> > PrepareWorkItem(ITaskItem xunitProject) { // Forces this task to run asynchronously await Task.Yield(); if (!xunitProject.GetRequiredMetadata(Log, "PublishDirectory", out string publishDirectory)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "TargetPath", out string targetPath)) { return(null); } if (!xunitProject.GetRequiredMetadata(Log, "RuntimeTargetFramework", out string runtimeTargetFramework)) { return(null); } xunitProject.TryGetMetadata("Arguments", out string arguments); string assemblyName = Path.GetFileName(targetPath); string driver = $"{PathToDotnet}"; // netfx tests should only run on Windows full framework for testing VS scenarios // These tests have to be executed slightly differently and we give them a different Identity so ADO can tell them apart var runtimeTargetFrameworkParsed = NuGetFramework.Parse(runtimeTargetFramework); var testIdentityDifferentiator = ""; bool netFramework = false; if (runtimeTargetFrameworkParsed.Framework == ".NETFramework") { testIdentityDifferentiator = ".netfx"; netFramework = true; } else if (runtimeTargetFrameworkParsed.Framework != ".NETCoreApp") { throw new NotImplementedException("does not support non support the runtime specified"); } // On mac due to https://github.com/dotnet/sdk/issues/3923, we run against workitem directory // but on Windows, if we running against working item diretory, we would hit long path. string testExecutionDirectory = netFramework ? "-e DOTNET_SDK_TEST_EXECUTION_DIRECTORY=%TestExecutionDirectory%" : IsPosixShell ? "-testExecutionDirectory $TestExecutionDirectory" : "-testExecutionDirectory %TestExecutionDirectory%"; string msbuildAdditionalSdkResolverFolder = netFramework ? "-e DOTNET_SDK_TEST_MSBUILDSDKRESOLVER_FOLDER=%HELIX_CORRELATION_PAYLOAD%\\r" : IsPosixShell ? "" : "-msbuildAdditionalSdkResolverFolder %HELIX_CORRELATION_PAYLOAD%\\r"; var scheduler = new AssemblyScheduler(methodLimit: 32); var assemblyPartitionInfos = scheduler.Schedule(targetPath, netFramework: netFramework); var partitionedWorkItem = new List <ITaskItem>(); foreach (var assemblyPartitionInfo in assemblyPartitionInfos) { string command = $"{driver} exec {assemblyName} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} {(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {assemblyPartitionInfo.ClassListArgumentString} {arguments}"; if (netFramework) { var testFilter = String.IsNullOrEmpty(assemblyPartitionInfo.ClassListArgumentString) ? "" : $"--filter \"{assemblyPartitionInfo.ClassListArgumentString}\""; command = $"{driver} test {assemblyName} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} {(XUnitArguments != null ? " " + XUnitArguments : "")} --results-directory .\\ --logger trx {testFilter}"; } Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); TimeSpan timeout = TimeSpan.FromMinutes(5); if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) { if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) { Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); } } partitionedWorkItem.Add(new Microsoft.Build.Utilities.TaskItem(assemblyPartitionInfo.DisplayName + testIdentityDifferentiator, new Dictionary <string, string>() { { "Identity", assemblyPartitionInfo.DisplayName + testIdentityDifferentiator }, { "PayloadDirectory", publishDirectory }, { "Command", command }, { "Timeout", timeout.ToString() }, })); } return(partitionedWorkItem); }