From 9673941dddfd7456608c185625994cd4d0adea82 Mon Sep 17 00:00:00 2001 From: kkeirstead <85592574+kkeirstead@users.noreply.github.com> Date: Fri, 20 Aug 2021 09:58:55 -0700 Subject: [PATCH] Execute Action - Testing (#742) --- dotnet-monitor.sln | 7 + ...nostics.Monitoring.ExecuteActionApp.csproj | 10 + .../Program.cs | 50 +++++ .../ExecuteActionTests.cs | 177 ++++++++++++++++++ src/Tools/dotnet-monitor/Strings.resx | 1 + 5 files changed, 245 insertions(+) create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Program.cs create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ExecuteActionTests.cs diff --git a/dotnet-monitor.sln b/dotnet-monitor.sln index 0ad570d658c..2d67f85e35a 100644 --- a/dotnet-monitor.sln +++ b/dotnet-monitor.sln @@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Tool.UnitTests", "src\Tests\Microsoft.Diagnostics.Monitoring.Tool.UnitTests\Microsoft.Diagnostics.Monitoring.Tool.UnitTests.csproj", "{0DBE362D-82F1-4740-AE6A-40C1A82EDCDB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.ExecuteActionApp", "src\Tests\Microsoft.Diagnostics.Monitoring.ExecuteActionApp\Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj", "{A5A0CAAB-C200-44D2-BC93-8445C6E748AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -92,6 +94,10 @@ Global {0DBE362D-82F1-4740-AE6A-40C1A82EDCDB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0DBE362D-82F1-4740-AE6A-40C1A82EDCDB}.Release|Any CPU.ActiveCfg = Release|Any CPU {0DBE362D-82F1-4740-AE6A-40C1A82EDCDB}.Release|Any CPU.Build.0 = Release|Any CPU + {A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +118,7 @@ Global {3AD0A40B-C569-4712-9764-7A788B9CD811} = {C7568468-1C79-4944-8136-18812A7F9EA7} {173F959B-231B-45D1-8328-9460D4C5BC71} = {19FAB78C-3351-4911-8F0C-8C6056401740} {0DBE362D-82F1-4740-AE6A-40C1A82EDCDB} = {C7568468-1C79-4944-8136-18812A7F9EA7} + {A5A0CAAB-C200-44D2-BC93-8445C6E748AD} = {C7568468-1C79-4944-8136-18812A7F9EA7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0} diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj new file mode 100644 index 00000000000..9ff1e502693 --- /dev/null +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj @@ -0,0 +1,10 @@ + + + Exe + netcoreapp3.1 + + + + + + diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Program.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Program.cs new file mode 100644 index 00000000000..9902f59ce7f --- /dev/null +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ExecuteActionApp/Program.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using Xunit; + +namespace Microsoft.Diagnostics.Monitoring.ExecuteActionApp +{ + internal class Program + { + public static int Main(string[] args) + { + string testType = args[0]; + + string[] testArgs = args.Skip(1).ToArray(); + + switch (testType) + { + case "ZeroExitCode": + Assert.Equal(0, testArgs.Length); + return 0; + + case "NonzeroExitCode": + Assert.Equal(0, testArgs.Length); + return 1; + + case "Sleep": + Assert.Equal(1, testArgs.Length); + string delayArg = testArgs[0]; + int delay = int.Parse(delayArg); + Thread.Sleep(delay); + return 0; + + case "TextFileOutput": + Assert.Equal(2, testArgs.Length); + string pathArg = testArgs[0]; + string contentsArg = testArgs[1]; + File.WriteAllText(pathArg, contentsArg); + return 0; + + default: + throw new ArgumentException($"Unknown test type {testType}."); + } + } + } +} diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ExecuteActionTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ExecuteActionTests.cs new file mode 100644 index 00000000000..6a0a6ad4b5a --- /dev/null +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ExecuteActionTests.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions; +using System.Threading.Tasks; +using Xunit; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Actions; +using System.Reflection; +using Microsoft.Diagnostics.Monitoring.TestCommon; +using System.Threading; +using System; +using System.IO; +using System.Diagnostics; +using Microsoft.Diagnostics.Tools.Monitor; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests +{ + public sealed class ExecuteActionTests + { + private const int TokenTimeoutMs = 10000; + private const int DelayMs = 1000; + + [Fact] + public async Task ExecuteAction_ZeroExitCode() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + options.Path = DotNetHost.HostExePath; + options.Arguments = GenerateArgumentsString(new string[] { "ZeroExitCode" }); + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + CollectionRuleActionResult result = await action.ExecuteAsync(options, null, cancellationTokenSource.Token); + + ValidateActionResult(result, "0"); + } + + [Fact] + public async Task ExecuteAction_NonzeroExitCode() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + options.Path = DotNetHost.HostExePath; + options.Arguments = GenerateArgumentsString(new string[] { "NonzeroExitCode" }); + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + InvalidOperationException invalidOperationException = await Assert.ThrowsAsync( + () => action.ExecuteAsync(options, null, cancellationTokenSource.Token)); + + Assert.Contains(string.Format(Strings.ErrorMessage_NonzeroExitCode, "1"), invalidOperationException.Message); + } + + [Fact] + public async Task ExecuteAction_TokenCancellation() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + options.Path = DotNetHost.HostExePath; + options.Arguments = GenerateArgumentsString(new string[] { "Sleep", (TokenTimeoutMs + DelayMs).ToString() }); ; + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + TaskCanceledException taskCanceledException = await Assert.ThrowsAsync( + () => action.ExecuteAsync(options, null, cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAction_TextFileOutput() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + DirectoryInfo outputDirectory = null; + + try + { + outputDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "ExecuteAction", Guid.NewGuid().ToString())); + string textFileOutputPath = Path.Combine(outputDirectory.FullName, "file.txt"); + + const string testMessage = "TestMessage"; + + options.Path = DotNetHost.HostExePath; + options.Arguments = GenerateArgumentsString(new string[] { "TextFileOutput", textFileOutputPath, testMessage }); + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + CollectionRuleActionResult result = await action.ExecuteAsync(options, null, cancellationTokenSource.Token); + + ValidateActionResult(result, "0"); + + Assert.Equal(testMessage, File.ReadAllText(textFileOutputPath)); + } + finally + { + try + { + outputDirectory?.Delete(recursive: true); + } + catch + { + } + } + } + + [Fact] + public async Task ExecuteAction_InvalidPath() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + string uniquePathName = Guid.NewGuid().ToString(); + + options.Path = uniquePathName; + options.Arguments = GenerateArgumentsString(Array.Empty()); + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + FileNotFoundException fileNotFoundException = await Assert.ThrowsAsync( + () => action.ExecuteAsync(options, null, cancellationTokenSource.Token)); + + Assert.Equal(string.Format(Strings.ErrorMessage_FileNotFound, uniquePathName), fileNotFoundException.Message); + } + + [Fact] + public async Task ExecuteAction_IgnoreExitCode() + { + ExecuteAction action = new(); + + ExecuteOptions options = new(); + + options.Path = DotNetHost.HostExePath; + options.Arguments = GenerateArgumentsString(new string[] { "NonzeroExitCode" }); + options.IgnoreExitCode = true; + + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); + + CollectionRuleActionResult result = await action.ExecuteAsync(options, null, cancellationTokenSource.Token); + + ValidateActionResult(result, "1"); + } + + private static string GenerateArgumentsString(string[] additionalArgs) + { + Assembly currAssembly = Assembly.GetExecutingAssembly(); + + List args = new(); + + // Entrypoint assembly + args.Add(AssemblyHelper.GetAssemblyArtifactBinPath(currAssembly, "Microsoft.Diagnostics.Monitoring.ExecuteActionApp", TargetFrameworkMoniker.NetCoreApp31)); + + // Entrypoint arguments + args.AddRange(additionalArgs); + + return string.Join(' ', args); + } + + private static void ValidateActionResult(CollectionRuleActionResult result, string expectedExitCode) + { + string actualExitCode; + + Assert.NotNull(result.OutputValues); + Assert.True(result.OutputValues.TryGetValue("ExitCode", out actualExitCode)); + Assert.Equal(expectedExitCode, actualExitCode); + } + } +} diff --git a/src/Tools/dotnet-monitor/Strings.resx b/src/Tools/dotnet-monitor/Strings.resx index 0af867e591f..48e109eaed2 100644 --- a/src/Tools/dotnet-monitor/Strings.resx +++ b/src/Tools/dotnet-monitor/Strings.resx @@ -266,6 +266,7 @@ Unable to start: {0} {1} + {0} = FileName, {1} = Process's Arguments Unhandled connection mode: {0}