diff --git a/src/Agent.Worker/Handlers/HandlerFactory.cs b/src/Agent.Worker/Handlers/HandlerFactory.cs index ee312b7358..45fce00e61 100644 --- a/src/Agent.Worker/Handlers/HandlerFactory.cs +++ b/src/Agent.Worker/Handlers/HandlerFactory.cs @@ -76,12 +76,16 @@ public IHandler Create( } else if (data is PowerShell3HandlerData) { - #pragma warning disable CA1416 // PowerShell handlers are Windows only - // PowerShell3. + #pragma warning disable CA1416 // PowerShell3 handler is Windows only handler = HostContext.CreateService(); (handler as IPowerShell3Handler).Data = data as PowerShell3HandlerData; #pragma warning restore CA1416 } + else if (data is PwshHandlerData) + { + handler = HostContext.CreateService(); + (handler as IPwshHandler).Data = data as PwshHandlerData; + } else if (data is PowerShellExeHandlerData) { #pragma warning disable CA1416 // PowerShell handlers are Windows only @@ -169,4 +173,4 @@ private List getTaskExceptionList() return exceptionList; } } -} \ No newline at end of file +} diff --git a/src/Agent.Worker/Handlers/PwshHandler.cs b/src/Agent.Worker/Handlers/PwshHandler.cs new file mode 100644 index 0000000000..5f378e5b89 --- /dev/null +++ b/src/Agent.Worker/Handlers/PwshHandler.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Agent.Sdk; +using Agent.Sdk.Knob; +using Microsoft.VisualStudio.Services.Agent.Util; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.Services.Agent.Worker.Handlers +{ + [ServiceLocator(Default = typeof(PwshHandler))] + public interface IPwshHandler : IHandler + { + PwshHandlerData Data { get; set; } + } + + public sealed class PwshHandler : Handler, IPwshHandler + { + public PwshHandlerData Data { get; set; } + + public async Task RunAsync() + { + Trace.Entering(); + ArgUtil.NotNull(Data, nameof(Data)); + ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); + ArgUtil.NotNull(Inputs, nameof(Inputs)); + ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); + + AddInputsToEnvironment(); + AddEndpointsToEnvironment(); + AddSecureFilesToEnvironment(); + AddVariablesToEnvironment(); + AddTaskVariablesToEnvironment(); + AddPrependPathToEnvironment(); + if (PlatformUtil.RunningOnWindows) + { + RemovePSModulePathFromEnvironment(); + } + + string scriptFile = ResolveScriptFile(); + string scriptDirectory = Path.GetDirectoryName(scriptFile); + string moduleFile = ResolveModuleFile(scriptDirectory); + string pwshArgs = BuildPwshArguments(moduleFile, scriptFile); + string pwsh = ResolvePwshExecutable(); + + StepHost.OutputDataReceived += OnDataReceived; + StepHost.ErrorDataReceived += OnDataReceived; + + var sigintTimeout = TimeSpan.FromMilliseconds(AgentKnobs.ProccessSigintTimeout.GetValue(ExecutionContext).AsInt()); + var sigtermTimeout = TimeSpan.FromMilliseconds(AgentKnobs.ProccessSigtermTimeout.GetValue(ExecutionContext).AsInt()); + var useGracefulShutdown = AgentKnobs.UseGracefulProcessShutdown.GetValue(ExecutionContext).AsBoolean(); + + try + { + await StepHost.ExecuteAsync( + workingDirectory: StepHost.ResolvePathForStepHost(scriptDirectory), + fileName: pwsh, + arguments: pwshArgs, + environment: Environment, + requireExitCodeZero: true, + outputEncoding: null, + killProcessOnCancel: false, + inheritConsoleHandler: !ExecutionContext.Variables.Retain_Default_Encoding, + continueAfterCancelProcessTreeKillAttempt: _continueAfterCancelProcessTreeKillAttempt, + sigintTimeout: sigintTimeout, + sigtermTimeout: sigtermTimeout, + useGracefulShutdown: useGracefulShutdown, + cancellationToken: ExecutionContext.CancellationToken); + } + finally + { + StepHost.OutputDataReceived -= OnDataReceived; + StepHost.ErrorDataReceived -= OnDataReceived; + } + } + + private void OnDataReceived(object sender, ProcessDataReceivedEventArgs e) + { + if (!CommandManager.TryProcessCommand(ExecutionContext, e.Data)) + { + ExecutionContext.Output(e.Data); + } + } + + private string ResolveScriptFile() + { + ArgUtil.NotNullOrEmpty(Data.Target, nameof(Data.Target)); + string scriptFile = Path.Combine(TaskDirectory, Data.Target); + ArgUtil.File(scriptFile, nameof(scriptFile)); + return scriptFile; + } + + private string ResolveModuleFile(string scriptDirectory) + { + string moduleFile = Path.Combine(scriptDirectory, "ps_modules", "VstsTaskSdk", "VstsTaskSdk.psd1"); + ArgUtil.File(moduleFile, nameof(moduleFile)); + return moduleFile; + } + + private string BuildPwshArguments(string moduleFile, string scriptFile) + { + if (AgentKnobs.UsePSScriptWrapper.GetValue(ExecutionContext).AsBoolean()) + { + return BuildWrapperArguments(moduleFile, scriptFile); + } + + return BuildDirectInvocationArguments(moduleFile, scriptFile); + } + + private string BuildWrapperArguments(string moduleFile, string scriptFile) + { + return StringUtil.Format( + @"-NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command ""{3}"" -VstsSdkPath {0} -DebugOption {1} -ScriptBlockString ""{2}""", + StepHost.ResolvePathForStepHost(moduleFile).Replace("'", "''"), + ExecutionContext.Variables.System_Debug == true ? "Continue" : "SilentlyContinue", + StepHost.ResolvePathForStepHost(scriptFile).Replace("'", "''''"), + Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "powershell", "Start-AzpTask.ps1")); + } + + private string BuildDirectInvocationArguments(string moduleFile, string scriptFile) + { + return StringUtil.Format( + @"-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "". ([scriptblock]::Create('if ([Console]::InputEncoding -is [Text.UTF8Encoding] -and [Console]::InputEncoding.GetPreamble().Length -ne 0) {{ [Console]::InputEncoding = New-Object Text.UTF8Encoding $false }}')) 2>&1 | ForEach-Object {{ Write-Verbose $_.Exception.Message -Verbose }} ; Import-Module -Name '{0}' -ArgumentList @{{ NonInteractive = $true }} -ErrorAction Stop ; $VerbosePreference = '{1}' ; $DebugPreference = '{1}' ; Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create('. ''{2}'''))""", + StepHost.ResolvePathForStepHost(moduleFile).Replace("'", "''"), + ExecutionContext.Variables.System_Debug == true ? "Continue" : "SilentlyContinue", + StepHost.ResolvePathForStepHost(scriptFile).Replace("'", "''''")); + } + + private string ResolvePwshExecutable() + { + string pwsh = "pwsh"; + if (StepHost is DefaultStepHost) + { + pwsh = HostContext.GetService().GetPath(); + } + + ArgUtil.NotNullOrEmpty(pwsh, nameof(pwsh)); + return pwsh; + } + } +} diff --git a/src/Agent.Worker/TaskManager.cs b/src/Agent.Worker/TaskManager.cs index 65847fecbd..8f7ed0e8a1 100644 --- a/src/Agent.Worker/TaskManager.cs +++ b/src/Agent.Worker/TaskManager.cs @@ -616,6 +616,7 @@ public sealed class ExecutionData private Node16HandlerData _node16; private Node20_1HandlerData _node20_1; private Node24HandlerData _node24; + private PwshHandlerData _pwsh; private PowerShellHandlerData _powerShell; private PowerShell3HandlerData _powerShell3; private PowerShellExeHandlerData _powerShellExe; @@ -713,6 +714,21 @@ public Node24HandlerData Node24 } } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public PwshHandlerData Pwsh + { + get + { + return _pwsh; + } + + set + { + _pwsh = value; + Add(value); + } + } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public PowerShellHandlerData PowerShell { @@ -916,6 +932,11 @@ public sealed class PowerShell3HandlerData : HandlerData public override int Priority => 106; } + public sealed class PwshHandlerData : HandlerData + { + public override int Priority => 106; + } + public sealed class PowerShellHandlerData : HandlerData { public string ArgumentFormat diff --git a/src/Agent.Worker/TaskRunner.cs b/src/Agent.Worker/TaskRunner.cs index e8e57d4f5c..9eecb1e6e9 100644 --- a/src/Agent.Worker/TaskRunner.cs +++ b/src/Agent.Worker/TaskRunner.cs @@ -202,7 +202,7 @@ private async Task RunAsyncInternal() runtimeVariables = new Variables(HostContext, variableCopy, out expansionWarnings); expansionWarnings?.ForEach(x => ExecutionContext.Warning(x)); } - else if (handlerData is BaseNodeHandlerData || handlerData is PowerShell3HandlerData) + else if (handlerData is BaseNodeHandlerData || handlerData is PowerShell3HandlerData || handlerData is PwshHandlerData) { // Only the node, node10, and powershell3 handlers support running inside container. // Make sure required container is already created. @@ -580,7 +580,7 @@ public HandlerData GetHandlerData(IExecutionContext ExecutionContext, ExecutionD targetOS = stepTarget.ExecutionOS; if (stepTarget is ContainerInfo) { - if ((currentExecution.All.Any(x => x is PowerShell3HandlerData)) && + if ((currentExecution.All.Any(x => x is PowerShell3HandlerData || x is PwshHandlerData)) && (currentExecution.All.Any(x => x is BaseNodeHandlerData))) { Trace.Info($"Since we are targeting a container, we will prefer a node handler if one is available"); @@ -590,7 +590,7 @@ public HandlerData GetHandlerData(IExecutionContext ExecutionContext, ExecutionD } Trace.Info($"Get handler data for target platform {targetOS.ToString()}"); return currentExecution.All - .OrderBy(x => !(x.PreferredOnPlatform(targetOS) && (preferPowershellHandler || !(x is PowerShell3HandlerData)))) // Sort true to false. + .OrderBy(x => !(x.PreferredOnPlatform(targetOS) && (preferPowershellHandler || !(x is PowerShell3HandlerData || x is PwshHandlerData)))) // Sort true to false. .ThenBy(x => x.Priority) .FirstOrDefault(); } @@ -742,9 +742,9 @@ private bool IsCorrelationIdRequired(IHandler handler, Definition task) Trace.Info($"Node SDK version: {nodeSdkVer}. Correlation ID is required: {isIdRequired}."); } } - else if (handler is IPowerShell3Handler) + else if (handler is IPowerShell3Handler || handler is IPwshHandler) { - Trace.Info("Current handler is PowerShell3. Trying to determing the SDK version."); + Trace.Info("Current handler is PowerShell. Trying to determing the SDK version."); var psSdkVer = task.GetPowerShellSDKVersion(); if (psSdkVer == null) { @@ -766,4 +766,4 @@ private bool IsCorrelationIdRequired(IHandler handler, Definition task) return isIdRequired; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.VisualStudio.Services.Agent/Capabilities/PwshCapabilitiesProvider.cs b/src/Microsoft.VisualStudio.Services.Agent/Capabilities/PwshCapabilitiesProvider.cs new file mode 100644 index 0000000000..39b3486c5b --- /dev/null +++ b/src/Microsoft.VisualStudio.Services.Agent/Capabilities/PwshCapabilitiesProvider.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.VisualStudio.Services.Agent.Util; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.Services.Agent.Capabilities +{ + public sealed class PwshCapabilitiesProvider : AgentService, ICapabilitiesProvider + { + public Type ExtensionType => typeof(ICapabilitiesProvider); + + public int Order => 2; + + public Task> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) + { + Trace.Entering(); + var capabilities = new List(); + + try + { + string pwsh = HostContext.GetService().GetPath(); + capabilities.Add(new Capability("Pwsh", pwsh)); + } + catch (Exception ex) + { + Trace.Info($"Pwsh capability not detected: {ex.Message}"); + } + + return Task.FromResult(capabilities); + } + } +} diff --git a/src/Microsoft.VisualStudio.Services.Agent/ExtensionManager.cs b/src/Microsoft.VisualStudio.Services.Agent/ExtensionManager.cs index db95050741..79955f02c2 100644 --- a/src/Microsoft.VisualStudio.Services.Agent/ExtensionManager.cs +++ b/src/Microsoft.VisualStudio.Services.Agent/ExtensionManager.cs @@ -46,6 +46,7 @@ private List LoadExtensions() where T : class, IExtension case "Microsoft.VisualStudio.Services.Agent.Capabilities.ICapabilitiesProvider": Add(extensions, "Microsoft.VisualStudio.Services.Agent.Capabilities.AgentCapabilitiesProvider, Microsoft.VisualStudio.Services.Agent"); Add(extensions, "Microsoft.VisualStudio.Services.Agent.Capabilities.EnvironmentCapabilitiesProvider, Microsoft.VisualStudio.Services.Agent"); + Add(extensions, "Microsoft.VisualStudio.Services.Agent.Capabilities.PwshCapabilitiesProvider, Microsoft.VisualStudio.Services.Agent"); if (PlatformUtil.RunningOnLinux || PlatformUtil.RunningOnMacOS) { Add(extensions, "Microsoft.VisualStudio.Services.Agent.Capabilities.NixCapabilitiesProvider, Microsoft.VisualStudio.Services.Agent"); @@ -145,4 +146,4 @@ private void Add(List extensions, string assemblyQualifiedName) w extensions.Add(extension); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.VisualStudio.Services.Agent/Util/PwshExeUtil.cs b/src/Microsoft.VisualStudio.Services.Agent/Util/PwshExeUtil.cs new file mode 100644 index 0000000000..9be887d91a --- /dev/null +++ b/src/Microsoft.VisualStudio.Services.Agent/Util/PwshExeUtil.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Agent.Sdk; +using Agent.Sdk.Util; +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; + +namespace Microsoft.VisualStudio.Services.Agent.Util +{ + [ServiceLocator(Default = typeof(PwshExeUtil))] + public interface IPwshExeUtil : IAgentService + { + string GetPath(); + } + + public sealed class PwshExeUtil : AgentService, IPwshExeUtil + { + private static readonly Version MinimumVersion = new Version(7, 0); + + public string GetPath() + { + Trace.Entering(); + + string commandName = PlatformUtil.RunningOnWindows ? "pwsh.exe" : "pwsh"; + string pwshPath = WhichUtil.Which(commandName, trace: Trace); + if (string.IsNullOrEmpty(pwshPath)) + { + throw new InvalidOperationException(StringUtil.Loc("FileNotFound", commandName)); + } + + Version version = GetVersion(pwshPath); + if (version < MinimumVersion) + { + throw new InvalidOperationException($"A compatible version of pwsh was not found. Minimum required version is {MinimumVersion}."); + } + + return pwshPath; + } + + private Version GetVersion(string pwshPath) + { + ArgUtil.NotNullOrEmpty(pwshPath, nameof(pwshPath)); + + string output = string.Empty; + using (var process = new Process()) + { + process.StartInfo = new ProcessStartInfo + { + FileName = pwshPath, + Arguments = "-NoLogo -NoProfile -NonInteractive -Command \"$PSVersionTable.PSVersion.ToString()\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + if (!process.Start()) + { + throw new InvalidOperationException($"Unable to start '{pwshPath}'."); + } + + output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Unable to determine pwsh version from '{pwshPath}'. {error}".Trim()); + } + } + + string versionString = output?.Trim(); + if (Version.TryParse(versionString, out Version version)) + { + return version; + } + + Match match = Regex.Match(versionString ?? string.Empty, @"\d+\.\d+(\.\d+)?(\.\d+)?", RegexOptions.CultureInvariant); + if (match.Success && Version.TryParse(match.Value, out version)) + { + return version; + } + + throw new InvalidOperationException($"Unable to parse pwsh version '{versionString}'."); + } + } +} diff --git a/src/Test/L0/ExtensionManagerL0.cs b/src/Test/L0/ExtensionManagerL0.cs index c3cf575e07..eb53278734 100644 --- a/src/Test/L0/ExtensionManagerL0.cs +++ b/src/Test/L0/ExtensionManagerL0.cs @@ -48,6 +48,9 @@ public void LoadsTypes() AssertContains( manager, concreteType: typeof(Microsoft.VisualStudio.Services.Agent.Capabilities.AgentCapabilitiesProvider)); + AssertContains( + manager, + concreteType: typeof(Microsoft.VisualStudio.Services.Agent.Capabilities.PwshCapabilitiesProvider)); AssertContains( manager, concreteType: typeof(Microsoft.VisualStudio.Services.Agent.Worker.Build.BuildJobExtension)); diff --git a/src/Test/L0/Listener/Configuration/PwshCapabilitiesProviderTestL0.cs b/src/Test/L0/Listener/Configuration/PwshCapabilitiesProviderTestL0.cs new file mode 100644 index 0000000000..c0cbf1f7e1 --- /dev/null +++ b/src/Test/L0/Listener/Configuration/PwshCapabilitiesProviderTestL0.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.VisualStudio.Services.Agent.Capabilities; +using Microsoft.VisualStudio.Services.Agent.Util; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.VisualStudio.Services.Agent.Tests.Listener +{ + public sealed class PwshCapabilitiesProviderTestL0 + { + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Agent")] + public async Task AddsPwshCapabilityWhenPwshExists() + { + using var hc = new TestHostContext(this); + using var tokenSource = new CancellationTokenSource(); + + var pwshUtil = new Mock(); + pwshUtil.Setup(x => x.GetPath()).Returns("/usr/bin/pwsh"); + hc.SetSingleton(pwshUtil.Object); + + var provider = new PwshCapabilitiesProvider(); + provider.Initialize(hc); + + List capabilities = await provider.GetCapabilitiesAsync(new AgentSettings(), tokenSource.Token); + + Capability pwshCapability = capabilities.SingleOrDefault(x => string.Equals(x.Name, "Pwsh", StringComparison.Ordinal)); + Assert.NotNull(pwshCapability); + Assert.Equal("/usr/bin/pwsh", pwshCapability.Value); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Agent")] + public async Task ReturnsNoPwshCapabilityWhenPwshIsUnavailable() + { + using var hc = new TestHostContext(this); + using var tokenSource = new CancellationTokenSource(); + + var pwshUtil = new Mock(); + pwshUtil.Setup(x => x.GetPath()).Throws(new InvalidOperationException("pwsh missing")); + hc.SetSingleton(pwshUtil.Object); + + var provider = new PwshCapabilitiesProvider(); + provider.Initialize(hc); + + List capabilities = await provider.GetCapabilitiesAsync(new AgentSettings(), tokenSource.Token); + + Assert.DoesNotContain(capabilities, x => string.Equals(x.Name, "Pwsh", StringComparison.Ordinal)); + } + } +} diff --git a/src/Test/L0/Worker/Handlers/HandlerFactoryL0.cs b/src/Test/L0/Worker/Handlers/HandlerFactoryL0.cs new file mode 100644 index 0000000000..cc94006e48 --- /dev/null +++ b/src/Test/L0/Worker/Handlers/HandlerFactoryL0.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Agent.Sdk; +using Microsoft.TeamFoundation.DistributedTask.WebApi; +using Microsoft.VisualStudio.Services.Agent.Util; +using Microsoft.VisualStudio.Services.Agent.Worker; +using Microsoft.VisualStudio.Services.Agent.Worker.Handlers; +using Moq; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.VisualStudio.Services.Agent.Tests.Worker.Handlers +{ + public sealed class HandlerFactoryL0 + { + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void CreatesPwshHandlerForPwshHandlerData() + { + using var hc = new TestHostContext(this); + hc.SetSingleton(new WorkerCommandManager() as IWorkerCommandManager); + hc.SetSingleton(new ExtensionManager() as IExtensionManager); + hc.EnqueueInstance(new PwshHandler()); + + var executionContext = new Mock(); + List warnings; + executionContext.Setup(x => x.Variables).Returns(new Variables(hc, new Dictionary(), out warnings)); + executionContext.Setup(x => x.Endpoints).Returns(new List()); + executionContext.Setup(x => x.TaskVariables).Returns(new Variables(hc, new Dictionary(), out warnings)); + executionContext.Setup(x => x.PrependPath).Returns(new List()); + executionContext.Setup(x => x.GetScopedEnvironment()).Returns(new SystemEnvironment()); + + var factory = new HandlerFactory(); + factory.Initialize(hc); + + IHandler handler = factory.Create( + executionContext: executionContext.Object, + task: null, + stepHost: new Mock().Object, + endpoints: new List(), + secureFiles: new List(), + data: new PwshHandlerData(), + inputs: new Dictionary(), + environment: new Dictionary(VarUtil.EnvironmentVariableKeyComparer), + runtimeVariables: executionContext.Object.Variables, + taskDirectory: hc.GetDirectory(WellKnownDirectory.Work)); + + Assert.IsType(handler); + } + } +} diff --git a/src/Test/L0/Worker/TaskRunnerL0.cs b/src/Test/L0/Worker/TaskRunnerL0.cs index a2c0b26035..dd9bfb7b2f 100644 --- a/src/Test/L0/Worker/TaskRunnerL0.cs +++ b/src/Test/L0/Worker/TaskRunnerL0.cs @@ -75,9 +75,15 @@ public void GetHandlerHostOnlyTests() var powerShell3Data = new PowerShell3HandlerData() { Platforms = new string[] { "windows" } }; var ps3OnlyExecutionData = new ExecutionData(); ps3OnlyExecutionData.PowerShell3 = powerShell3Data; + var pwshData = new PwshHandlerData() { Platforms = new string[] { "windows", "linux", "osx" } }; + var pwshOnlyExecutionData = new ExecutionData(); + pwshOnlyExecutionData.Pwsh = pwshData; var mixedExecutionData = new ExecutionData(); mixedExecutionData.PowerShell3 = powerShell3Data; mixedExecutionData.Node = nodeData; + var mixedPwshExecutionData = new ExecutionData(); + mixedPwshExecutionData.Pwsh = pwshData; + mixedPwshExecutionData.Node = nodeData; foreach (var test in new GetHandlerTest[] { @@ -88,9 +94,15 @@ public void GetHandlerHostOnlyTests() new GetHandlerTest() { Name="PowerShell3 Only on Windows", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows }, new GetHandlerTest() { Name="PowerShell3 Only on Linux", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Linux }, new GetHandlerTest() { Name="PowerShell3 Only on OSX", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.OSX }, + new GetHandlerTest() { Name="Pwsh Only on Windows", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows }, + new GetHandlerTest() { Name="Pwsh Only on Linux", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Linux }, + new GetHandlerTest() { Name="Pwsh Only on OSX", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.OSX }, new GetHandlerTest() { Name="Mixed on Windows", Input=mixedExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows }, new GetHandlerTest() { Name="Mixed on Linux", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux }, new GetHandlerTest() { Name="Mixed on OSX", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX }, + new GetHandlerTest() { Name="Mixed Pwsh on Windows", Input=mixedPwshExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows }, + new GetHandlerTest() { Name="Mixed Pwsh on Linux", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux }, + new GetHandlerTest() { Name="Mixed Pwsh on OSX", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX }, }) { using (TestHostContext hc = CreateTestContext(test.Name)) @@ -111,9 +123,15 @@ public void GetHandlerContainerTargetTests() var powerShell3Data = new PowerShell3HandlerData() { Platforms = new string[] { "windows" } }; var ps3OnlyExecutionData = new ExecutionData(); ps3OnlyExecutionData.PowerShell3 = powerShell3Data; + var pwshData = new PwshHandlerData() { Platforms = new string[] { "windows", "linux", "osx" } }; + var pwshOnlyExecutionData = new ExecutionData(); + pwshOnlyExecutionData.Pwsh = pwshData; var mixedExecutionData = new ExecutionData(); mixedExecutionData.Node = nodeData; mixedExecutionData.PowerShell3 = powerShell3Data; + var mixedPwshExecutionData = new ExecutionData(); + mixedPwshExecutionData.Node = nodeData; + mixedPwshExecutionData.Pwsh = pwshData; ContainerInfo containerInfo = new ContainerInfo() { }; @@ -125,9 +143,15 @@ public void GetHandlerContainerTargetTests() new GetHandlerTest() { Name="PowerShell3 Only on Windows", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, new GetHandlerTest() { Name="PowerShell3 Only on Linux", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, new GetHandlerTest() { Name="PowerShell3 Only on OSX", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on Windows", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on Linux", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on OSX", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on Windows", Input=mixedExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on Linux", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on OSX", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on Windows", Input=mixedPwshExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on Linux", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on OSX", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, }) { using (TestHostContext hc = CreateTestContext(test.Name)) @@ -148,9 +172,15 @@ public void GetHandlerContainerTargetPreferNodeDisabledTests() var powerShell3Data = new PowerShell3HandlerData() { Platforms = new string[] { "windows" } }; var ps3OnlyExecutionData = new ExecutionData(); ps3OnlyExecutionData.PowerShell3 = powerShell3Data; + var pwshData = new PwshHandlerData() { Platforms = new string[] { "windows", "linux", "osx" } }; + var pwshOnlyExecutionData = new ExecutionData(); + pwshOnlyExecutionData.Pwsh = pwshData; var mixedExecutionData = new ExecutionData(); mixedExecutionData.Node = nodeData; mixedExecutionData.PowerShell3 = powerShell3Data; + var mixedPwshExecutionData = new ExecutionData(); + mixedPwshExecutionData.Node = nodeData; + mixedPwshExecutionData.Pwsh = pwshData; ContainerInfo containerInfo = new ContainerInfo() { }; @@ -162,9 +192,15 @@ public void GetHandlerContainerTargetPreferNodeDisabledTests() new GetHandlerTest() { Name="PowerShell3 Only on Windows", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, new GetHandlerTest() { Name="PowerShell3 Only on Linux", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, new GetHandlerTest() { Name="PowerShell3 Only on OSX", Input=ps3OnlyExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on Windows", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on Linux", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Pwsh Only on OSX", Input=pwshOnlyExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on Windows", Input=mixedExecutionData, Expected=powerShell3Data, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on Linux", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, new GetHandlerTest() { Name="Mixed on OSX", Input=mixedExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on Windows", Input=mixedPwshExecutionData, Expected=pwshData, HostOS=PlatformUtil.OS.Windows, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on Linux", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.Linux, StepTarget=containerInfo }, + new GetHandlerTest() { Name="Mixed Pwsh on OSX", Input=mixedPwshExecutionData, Expected=nodeData, HostOS=PlatformUtil.OS.OSX, StepTarget=containerInfo }, }) { var variables = new Dictionary(); @@ -223,4 +259,4 @@ public void VerifyTasksTests() } } } -} \ No newline at end of file +}