-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Execute Action - Testing #742
Changes from 7 commits
d7c5e29
1014578
1dc9038
3512c06
820a580
7878c01
bd42c82
21ad564
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFrameworks>netcoreapp3.1</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Microsoft.Diagnostics.Monitoring.TestCommon\Microsoft.Diagnostics.Monitoring.TestCommon.csproj" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}."); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// 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; | ||
|
||
namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests | ||
{ | ||
public sealed class ExecuteActionTests | ||
jander-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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<InvalidOperationException>( | ||
() => 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() }); ; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might work most of the time. If it's not stable enough, maybe we can change this to write out a file (like the TextFileOutput scenario) and then sleep for some large amount of time. Back in the test, we start the process, wait for the file to appear, and then immediately cancel the token. This would provide more determinism so that if the host happens to be really slow that process startup doesn't exceed the token timeout. |
||
|
||
using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); | ||
|
||
TaskCanceledException taskCanceledException = await Assert.ThrowsAsync<TaskCanceledException>( | ||
() => 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<string>()); | ||
|
||
using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TokenTimeoutMs); | ||
|
||
FileNotFoundException fileNotFoundException = await Assert.ThrowsAsync<FileNotFoundException>( | ||
() => 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(); | ||
|
||
return AssemblyHelper.GetAssemblyArtifactBinPath(currAssembly, "Microsoft.Diagnostics.Monitoring.ExecuteActionApp", TargetFrameworkMoniker.NetCoreApp31) | ||
kkeirstead marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ ' ' + string.Join(' ', additionalArgs); | ||
} | ||
|
||
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); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider some argument validation to make it easier to figure out failures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What did you have in mind? In theory, since for each test we're manually setting the args, shouldn't we know that they'll all be valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example:
args
parameter. This allows us to change the ordering of the arguments passed on the command line without having to rewrite all of the test cases.