|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.Extensions.FileSystemGlobbing;
namespace Microsoft.DotNet.Cli.Commands.Test;
internal sealed class TestModulesFilterHandler : ITestHandler
{
private readonly string _testModules;
private readonly string? _testModulesRoot;
private readonly List<string> _testModulePaths;
public TestModulesFilterHandler(string testModules, ParseResult parseResult)
{
_testModules = testModules;
var definition = (TestCommandDefinition.MicrosoftTestingPlatform)parseResult.CommandResult.Command;
// If the module path pattern(s) was provided, we will use that to filter the test modules
// If the root directory was provided, we will use that to search for the test modules
// Otherwise, we will use the current directory
string? rootDirectory = Directory.GetCurrentDirectory();
if (parseResult.HasOption(definition.TestModulesRootDirectoryOption))
{
rootDirectory = parseResult.GetValue(definition.TestModulesRootDirectoryOption);
}
_testModulesRoot = rootDirectory;
_testModulePaths = GetMatchedModulePaths(_testModules, _testModulesRoot);
}
public bool Initialize()
{
// If the root directory is not valid, we simply return
if (string.IsNullOrEmpty(_testModulesRoot) || !Directory.Exists(_testModulesRoot))
{
Reporter.Output.WriteLine(string.Format(CliCommandStrings.CmdNonExistentRootDirectoryErrorDescription, _testModulesRoot).Yellow());
return false;
}
// If no matches were found, we simply return
if (_testModulePaths.Count == 0)
{
Reporter.Output.WriteLine(string.Format(CliCommandStrings.CmdNoTestModulesErrorDescription, _testModules, _testModulesRoot).Yellow());
return false;
}
return true;
}
public int RunTestApplications(TestApplicationActionQueue actionQueue)
{
var muxerPath = new Muxer().MuxerPath;
foreach (string testModule in _testModulePaths)
{
// We want to produce the right RunCommand and RunArguments for TestApplication implementation to consume directly.
// We don't want TestApplication class to be concerned about whether it's running dll via test module or not.
// If we are given dll, we use dotnet exec. Otherwise, we run the executable directly.
RunProperties runProperties = testModule.HasExtension(CliConstants.DLLExtension)
? new RunProperties(muxerPath, $@"exec ""{testModule}""", null)
: new RunProperties(testModule, null, null);
var testApp = new ParallelizableTestModuleGroupWithSequentialInnerModules(new TestModule(runProperties, null, null, true, null, testModule, DotnetRootArchVariableName: null));
// Write the test application to the channel
actionQueue.Enqueue(testApp);
}
return actionQueue.CompleteEnqueueAndWait();
}
internal static List<string> GetMatchedModulePaths(string testModules, string? rootDirectory)
{
if (string.IsNullOrEmpty(rootDirectory))
{
return new List<string>();
}
Matcher matcher = new();
// Patterns are semicolon-separated. Whitespace around each pattern is trimmed so that
// user-friendly expressions such as `**/*.A.dll; **/*.B.dll` (e.g. coming from YAML folded
// scalars in CI pipelines) work as expected. A pattern that begins with '!' is treated
// as an exclude pattern, matching the convention used by VSTest's --test-modules and other
// common glob systems (npm, .gitignore).
foreach (var rawPattern in testModules.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
var pattern = rawPattern.Trim();
if (pattern.Length == 0)
{
continue;
}
if (pattern[0] == '!')
{
var excludePattern = pattern.Substring(1).TrimStart();
if (excludePattern.Length != 0)
{
matcher.AddExclude(excludePattern);
}
}
else
{
matcher.AddInclude(pattern);
}
}
// Make sure we have a non-lazy collection, so that if we enumerate multiple times we guarantee the same result.
var results = matcher.GetResultsInFullPath(rootDirectory);
if (results is List<string> resultsList)
{
return resultsList;
}
return results.ToList();
}
}
|