diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs index 0c228199141a..b4a67bed9016 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs @@ -6,6 +6,7 @@ namespace Microsoft.DotNet.Cli using System; using System.Collections.Generic; using System.Linq; + using static BlameArgs; /// /// Converts the given arguments to vstest parsable arguments @@ -62,6 +63,8 @@ public List Convert(string[] args, out List ignoredArgs) ignoredArgs = new List(); string activeArgument = null; + BlameArgs blame = new BlameArgs(); + foreach (var arg in args) { if (arg == "--") @@ -77,6 +80,10 @@ public List Convert(string[] args, out List ignoredArgs) { ignoredArgs.Add(activeArgument); } + else if (blame.IsBlameArg(activeArgument, null)) + { + // do nothing, we process remaining arguments ourselves + } else { newArgList.Add(activeArgument); @@ -101,6 +108,12 @@ public List Convert(string[] args, out List ignoredArgs) continue; } + if (blame.IsBlameArg(argValues[0], argValues[1])) + { + blame.UpdateBlame(argValues[0], argValues[1]); + continue; + } + // Check if the argument is shortname if (ArgumentMapping.TryGetValue(argValues[0].ToLower(), out var longName)) { @@ -111,10 +124,18 @@ public List Convert(string[] args, out List ignoredArgs) } else { - activeArgument = arg.ToLower(); - if (ArgumentMapping.TryGetValue(activeArgument, out var value)) + if (blame.IsBlameSwitch(arg)) { - activeArgument = value; + blame.UpdateBlame(arg, null); + activeArgument = arg; + } + else + { + activeArgument = arg.ToLower(); + if (ArgumentMapping.TryGetValue(activeArgument, out var value)) + { + activeArgument = value; + } } } } @@ -129,6 +150,10 @@ public List Convert(string[] args, out List ignoredArgs) ignoredArgs.Add(activeArgument); ignoredArgs.Add(arg); } + else if (blame.IsBlameArg(activeArgument, arg)) + { + blame.UpdateBlame(activeArgument, arg); + } else { newArgList.Add(string.Concat(activeArgument, ":", arg)); @@ -138,7 +163,14 @@ public List Convert(string[] args, out List ignoredArgs) } else { - newArgList.Add(arg); + if (blame.IsBlameArg(arg, null)) + { + blame.UpdateBlame(arg, null); + } + else + { + newArgList.Add(arg); + } } } @@ -148,12 +180,20 @@ public List Convert(string[] args, out List ignoredArgs) { ignoredArgs.Add(activeArgument); } + else if( blame.IsBlameArg(activeArgument, null)) { + // do nothing, we process remaining arguments ourselves + } else { newArgList.Add(activeArgument); } } + if (blame.Blame) + { + blame.AddCombinedBlameArgs(newArgList); + } + return newArgList; } @@ -172,4 +212,184 @@ private void UpdateVerbosity(string verbosity, List newArgList) newArgList.Add(verbosityString + verbosity); } } + + class BlameArgs + { + public bool Blame = false; + public string LegacyBlame = null; + + public bool CollectCrashDump = false; + public string CollectCrashDumpType = null; + public bool CollectCrashDumpAlways = false; + + public bool CollectHangDump = false; + public string CollectHangDumpType = null; + public string CollectHangDumpTimeout = null; + + public static string BlameParam = "--blame"; + public static string BlameCrashParam = "--blame-crash"; + public static string BlameCrashDumpTypeParam = "--blame-crash-dump-type"; + public static string BlameCrashCollectAlwaysParam = "--blame-crash-collect-always"; + public static string BlameHangParam = "--blame-hang"; + public static string BlameHangDumpTypeParam = "--blame-hang-dump-type"; + public static string BlameHangTimeoutParam = "--blame-hang-timeout"; + + // parameters that expect arguments + private readonly string[] _blameArgList = new string[]{ + BlameCrashDumpTypeParam, + + BlameHangDumpTypeParam, + BlameHangTimeoutParam + }; + + // parameters that don't expect any arguments + private readonly string[] _blameSwitchList = new string[]{ + BlameParam, + + BlameCrashParam, + BlameCrashCollectAlwaysParam, + + BlameHangParam, + }; + + + internal bool IsBlameArg(string parameter, string value) + { + return _blameArgList.Any(p => Eq(p, parameter)) || _blameSwitchList.Any(p => Eq(p, parameter)); + } + + private bool IsLegacyBlame(string parameter, string value) + { + // when provided --blame , we do not want to process it any further + // most likely a legacy call, and the param is already in the format that vstest.console expects + return Eq(BlameParam, parameter) && !string.IsNullOrWhiteSpace(value); + } + + internal bool IsBlame(string parameter) + { + return Eq(BlameParam, parameter); + + } + + internal bool IsBlameSwitch(string parameter) + { + return _blameSwitchList.Any(p => Eq(p, parameter)); + + } + + internal void UpdateBlame(string parameter, string argument) + { + if (IsLegacyBlame(parameter, argument)) + { + Blame = true; + LegacyBlame = argument; + } + + if (Eq(parameter, BlameParam)) + { + Blame = true; + } + + // Any blame-crash param implies that we collect crash dump + if (Eq(parameter, BlameCrashParam)) + { + Blame = true; + CollectCrashDump = true; + } + + if (Eq(parameter, BlameCrashCollectAlwaysParam)) + { + Blame = true; + CollectCrashDump = true; + CollectCrashDumpAlways = true; + } + + if (Eq(parameter, BlameCrashDumpTypeParam)) + { + Blame = true; + CollectCrashDump = true; + CollectCrashDumpType = argument; + } + + // Any Blame-hang param implies that we collect hang dump + if (Eq(parameter, BlameHangParam)) + { + Blame = true; + CollectHangDump = true; + } + + if (Eq(parameter, BlameHangDumpTypeParam)) + { + Blame = true; + CollectHangDump = true; + CollectHangDumpType = argument; + } + + if (Eq(parameter, BlameHangTimeoutParam)) + { + Blame = true; + CollectHangDump = true; + CollectHangDumpTimeout = argument; + } + } + + private bool Eq(string left, string right) + { + return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); + } + + internal void AddCombinedBlameArgs(List newArgList) + { + if (!Blame) + return; + + if (!string.IsNullOrWhiteSpace(LegacyBlame)) + { + // when legacy call is detected don't process + // any more parameters + newArgList.Add($"--blame:{LegacyBlame}"); + return; + } + + string crashDumpArgs = null; + string hangDumpArgs = null; + + if (CollectCrashDump) + { + crashDumpArgs = "CollectDump"; + if (CollectCrashDumpAlways) + { + crashDumpArgs += ";CollectAlways=true"; + } + + if (!string.IsNullOrWhiteSpace(CollectCrashDumpType)) + { + crashDumpArgs += $";DumpType={CollectCrashDumpType}"; + } + } + + if (CollectHangDump) + { + hangDumpArgs = "CollectHangDump"; + if (!string.IsNullOrWhiteSpace(CollectHangDumpType)) + { + hangDumpArgs += $";DumpType={CollectHangDumpType}"; + } + + if (!string.IsNullOrWhiteSpace(CollectHangDumpTimeout)) + { + hangDumpArgs += $";TestTimeout={CollectHangDumpTimeout}"; + } + } + + if (CollectCrashDump || CollectHangDump) + { + newArgList.Add($@"--blame:""{string.Join(";", crashDumpArgs, hangDumpArgs).Trim(';')}"""); + } + else + { + newArgList.Add("--blame"); + } + } + } } diff --git a/src/Tests/dotnet.Tests/ParserTests/VSTestArgumentConverterTests.cs b/src/Tests/dotnet.Tests/ParserTests/VSTestArgumentConverterTests.cs index a140b1150511..35a9a3c41f3b 100644 --- a/src/Tests/dotnet.Tests/ParserTests/VSTestArgumentConverterTests.cs +++ b/src/Tests/dotnet.Tests/ParserTests/VSTestArgumentConverterTests.cs @@ -21,7 +21,7 @@ public void ConvertArgsShouldConvertValidArgsIntoVSTestParsableArgs(string input // Act var convertedArgs = new VSTestArgumentConverter().Convert(args, out var ignoredArgs); - convertedArgs.Should().BeEquivalentTo(convertedArgs); + convertedArgs.Should().BeEquivalentTo(expectedArgs); ignoredArgs.Should().BeEmpty(); } @@ -35,7 +35,7 @@ public void ConvertArgshouldConvertsVerbosityArgsIntoVSTestParsableArgs(string i // Act var convertedArgs = new VSTestArgumentConverter().Convert(args, out var ignoredArgs); - convertedArgs.Should().BeEquivalentTo(convertedArgs); + convertedArgs.Should().BeEquivalentTo(expectedArgs); ignoredArgs.Should().BeEmpty(); } @@ -50,7 +50,7 @@ public void ConvertArgsShouldIgnoreKnownArgsWhileConvertingArgsIntoVSTestParsabl // Act var convertedArgs = new VSTestArgumentConverter().Convert(args, out var ignoredArgs); - convertedArgs.Should().BeEquivalentTo(convertedArgs); + convertedArgs.Should().BeEquivalentTo(expectedArgs); Assert.Equal(expIgnoredArgs, ignoredArgs); } @@ -69,20 +69,36 @@ public static class DataSource { public static IEnumerable ArgTestCases { get; } = new List { - new object[] { "-h", "--help" }, - new object[] { "sometest.dll -s test.settings", "sometest.dll --settings:test.settings" }, - new object[] { "sometest.dll -t", "sometest.dll --listtests" }, - new object[] { "sometest.dll --list-tests", "sometest.dll --listtests" }, - new object[] { "sometest.dll --filter", "sometest.dll --testcasefilter" }, - new object[] { "sometest.dll -l trx", "sometest.dll --logger:trx" }, - new object[] { "sometest.dll -a c:\adapterpath\temp", "sometest.dll --testadapterpath:c:\adapterpath\temp" }, - new object[] { "sometest.dll --test-adapter-path c:\adapterpath\temp", "sometest.dll --testadapterpath:c:\adapterpath\temp" }, - new object[] { "sometest.dll -f net451", "sometest.dll --framework:net451" }, + new object[] { @"-h", "--help" }, + new object[] { @"sometest.dll -s test.settings", @"sometest.dll --settings:test.settings" }, + new object[] { @"sometest.dll -t", @"sometest.dll --listtests" }, + new object[] { @"sometest.dll --list-tests", @"sometest.dll --listtests" }, + new object[] { @"sometest.dll --filter", @"sometest.dll --testcasefilter" }, + new object[] { @"sometest.dll -l trx", @"sometest.dll --logger:trx" }, + new object[] { @"sometest.dll -a c:\adapterpath\temp", @"sometest.dll --testadapterpath:c:\adapterpath\temp" }, + new object[] { @"sometest.dll --test-adapter-path c:\adapterpath\temp", @"sometest.dll --testadapterpath:c:\adapterpath\temp" }, + new object[] { @"sometest.dll -f net451", @"sometest.dll --framework:net451" }, new object[] { @"sometest.dll -d c:\temp\log.txt", @"sometest.dll --diag:c:\temp\log.txt" }, new object[] { @"sometest.dll --results-directory c:\temp\", @"sometest.dll --resultsdirectory:c:\temp\" }, new object[] { @"sometest.dll -s testsettings -t -a c:\path -f net451 -d log.txt --results-directory c:\temp\", @"sometest.dll --settings:testsettings --listtests --testadapterpath:c:\path --framework:net451 --diag:log.txt --resultsdirectory:c:\temp\" }, new object[] { @"sometest.dll -s:testsettings -t -a:c:\path -f:net451 -d:log.txt --results-directory:c:\temp\", @"sometest.dll --settings:testsettings --listtests --testadapterpath:c:\path --framework:net451 --diag:log.txt --resultsdirectory:c:\temp\" }, - new object[] { @"sometest.dll --settings testsettings -t --test-adapter-path c:\path --framework net451 --diag log.txt --results-directory c:\temp\", @"sometest.dll --settings:testsettings --listtests --testadapterpath:c:\path --framework:net451 --diag:log.txt --resultsdirectory:c:\temp\" } + new object[] { @"sometest.dll --settings testsettings -t --test-adapter-path c:\path --framework net451 --diag log.txt --results-directory c:\temp\", @"sometest.dll --settings:testsettings --listtests --testadapterpath:c:\path --framework:net451 --diag:log.txt --resultsdirectory:c:\temp\" }, + new object[] { @"sometest.dll --blame", @"sometest.dll --blame" }, + new object[] { @"sometest.dll --blame-crash", @"sometest.dll --blame:""CollectDump""" }, + new object[] { @"sometest.dll --blame-crash-dump-type full", @"sometest.dll --blame:""CollectDump;DumpType=full""" }, + new object[] { @"sometest.dll --blame-crash-collect-always", @"sometest.dll --blame:""CollectDump;CollectAlways=true""" }, + new object[] { @"sometest.dll --blame --blame-crash-dump-type full --blame-crash-collect-always", @"sometest.dll --blame:""CollectDump;CollectAlways=true;DumpType=full""" }, + new object[] { @"sometest.dll --blame-hang", @"sometest.dll --blame:""CollectHangDump""" }, + new object[] { @"sometest.dll --blame-hang-dump-type full", @"sometest.dll --blame:""CollectHangDump;DumpType=full""" }, + new object[] { @"sometest.dll --blame-hang-timeout 10min", @"sometest.dll --blame:""CollectHangDump;TestTimeout=10min""" }, + new object[] { @"sometest.dll --blame --blame-hang-dump-type full --blame-hang-timeout 10min", @"sometest.dll --blame:""CollectHangDump;DumpType=full;TestTimeout=10min""" }, + new object[] { @"sometest.dll --blame --blame-hang-dump-type full --blame-hang-timeout 10min --blame-crash-dump-type mini --blame-crash-collect-always", @"sometest.dll --blame:""CollectDump;CollectAlways=true;DumpType=mini;CollectHangDump;DumpType=full;TestTimeout=10min""" }, + // using the legacy --blame syntax when we provide the parameter that are already in vstest.console format still works + new object[] { @"sometest.dll --blame ""CollectDump;DumpType=full""", @"sometest.dll --blame:""CollectDump;DumpType=full""" }, + new object[] { @"sometest.dll --blame:""CollectDump;DumpType=full""", @"sometest.dll --blame:""CollectDump;DumpType=full""" }, + + + }; public static IEnumerable VerbosityTestCases { get; } = new List