Skip to content
Merged
228 changes: 224 additions & 4 deletions src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.DotNet.Cli
using System;
using System.Collections.Generic;
using System.Linq;
using static BlameArgs;

/// <summary>
/// Converts the given arguments to vstest parsable arguments
Expand Down Expand Up @@ -62,6 +63,8 @@ public List<string> Convert(string[] args, out List<string> ignoredArgs)
ignoredArgs = new List<string>();

string activeArgument = null;
BlameArgs blame = new BlameArgs();

foreach (var arg in args)
{
if (arg == "--")
Expand All @@ -77,6 +80,10 @@ public List<string> Convert(string[] args, out List<string> ignoredArgs)
{
ignoredArgs.Add(activeArgument);
}
else if (blame.IsBlameArg(activeArgument, null))
{
// do nothing, we process remaining arguments ourselves
}
else
{
newArgList.Add(activeArgument);
Expand All @@ -101,6 +108,12 @@ public List<string> Convert(string[] args, out List<string> 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))
{
Expand All @@ -111,10 +124,18 @@ public List<string> Convert(string[] args, out List<string> 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;
}
}
}
}
Expand All @@ -129,6 +150,10 @@ public List<string> Convert(string[] args, out List<string> ignoredArgs)
ignoredArgs.Add(activeArgument);
ignoredArgs.Add(arg);
}
else if (blame.IsBlameArg(activeArgument, arg))
{
blame.UpdateBlame(activeArgument, arg);
}
else
{
newArgList.Add(string.Concat(activeArgument, ":", arg));
Expand All @@ -138,7 +163,14 @@ public List<string> Convert(string[] args, out List<string> ignoredArgs)
}
else
{
newArgList.Add(arg);
if (blame.IsBlameArg(arg, null))
{
blame.UpdateBlame(arg, null);
}
else
{
newArgList.Add(arg);
}
}
}

Expand All @@ -148,12 +180,20 @@ public List<string> Convert(string[] args, out List<string> 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;
}

Expand All @@ -172,4 +212,184 @@ private void UpdateVerbosity(string verbosity, List<string> 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 <value>, 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<string> 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");
}
}
}
}
42 changes: 29 additions & 13 deletions src/Tests/dotnet.Tests/ParserTests/VSTestArgumentConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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();
}

Expand All @@ -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);
}

Expand All @@ -69,20 +69,36 @@ public static class DataSource
{
public static IEnumerable<object[]> ArgTestCases { get; } = new List<object[]>
{
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<object[]> VerbosityTestCases { get; } = new List<object[]>
Expand Down