diff --git a/tests/runtest.cmd b/tests/runtest.cmd
index e8a99e2cb8f2..3f5fd081e1f5 100644
--- a/tests/runtest.cmd
+++ b/tests/runtest.cmd
@@ -329,32 +329,24 @@ if not exist %_CoreFXTestHost%\dotnet.exe echo CoreFX test host not found, pleas
set /p _CoreFXTestRemoteURL=< "%__ProjectFilesDir%\CoreFX\CoreFXTestListURL.txt"
if not defined __CoreFXTestList ( set __CoreFXTestList=%__ProjectFilesDir%\CoreFX\TopN.CoreFX.Windows.issues.json )
-echo Downloading CoreFX Test Binaries
-echo "%_dotnet%" "%_CoreFXTestUtilitiesOutputPath%\%_CoreFXTestSetupUtilityName%.dll" --clean --outputDirectory "%_CoreFXTestBinariesPath%" --testListJsonPath "%__CoreFXTestList%" --testUrl "%_CoreFXTestRemoteURL%"
-call "%_dotnet%" "%_CoreFXTestUtilitiesOutputPath%\%_CoreFXTestSetupUtilityName%.dll" --clean --outputDirectory "%_CoreFXTestBinariesPath%" --testListJsonPath "%__CoreFXTestList%" --testUrl "%_CoreFXTestRemoteURL%"
-if errorlevel 1 (
- exit /b 1
-)
set _CoreFXTestExecutable=xunit.console.netcore.exe
-set _CoreFXTestExecutableArgs=-notrait category=nonnetcoreapptests -notrait category=nonwindowstests -notrait category=failing -notrait category=IgnoreForCI
+set _CoreFXTestExecutableArgs= --notrait nonnetcoreapptests --notrait nonwindowstests --notrait failing --notrait IgnoreForCI
REM Set the log file name to something Jenkins can understand
set _CoreFX_TestLogFileName=testResults.xml
-for /D %%i in ("%_CoreFXTestBinariesPath%\*") do (
- pushd %%i
- if not exist "%%i\%_CoreFXTestExecutable%" echo "Error running CoreFX tests - %_CoreFXTestExecutable% not found" && exit /b 1
-
- set _TestName=%%~nxi
- set _LogPath=%_CoreFXLogsDir%\!_TestName!
- if not exist "!_LogPath!" (mkdir "!_LogPath!")
-
- echo %__MsgPrefix%Running !_TestName!
- echo Writing logs to !_LogPath!
- echo To reproduce directly run:
- echo "%_CoreFXTestHost%\dotnet.exe" "%%i\%_CoreFXTestExecutable%" "%%i\!_TestName!.dll" @"%%i\!_TestName!.rsp" -xml "!_LogPath!\%_CoreFX_TestLogFileName%" %_CoreFXTestExecutableArgs%
- call "%_CoreFXTestHost%\dotnet.exe" "%%i\%_CoreFXTestExecutable%" "%%i\!_TestName!.dll" @"%%i\!_TestName!.rsp" -xml "!_LogPath!\\%_CoreFX_TestLogFileName%" %_CoreFXTestExecutableArgs%
- popd
+set _CoreFX_TestRunScriptName=CoreCLR_RunTest.cmd
+
+
+echo Downloading and Running CoreFX Test Binaries
+echo call "%_dotnet%" "%_CoreFXTestUtilitiesOutputPath%\%_CoreFXTestSetupUtilityName%.dll" --clean --outputDirectory "%_CoreFXTestBinariesPath%" --testListJsonPath "%__CoreFXTestList%" --testUrl "%_CoreFXTestRemoteURL%" --runTests --dotnetPath "%_CoreFXTestHost%\dotnet.exe" --executable %_CoreFXTestExecutable% --logPath %_CoreFXLogsDir% --maxProcessCount 5 %_CoreFXTestExecutableArgs%
+call "%_dotnet%" "%_CoreFXTestUtilitiesOutputPath%\%_CoreFXTestSetupUtilityName%.dll" --clean --outputDirectory "%_CoreFXTestBinariesPath%" --testListJsonPath "%__CoreFXTestList%" --testUrl "%_CoreFXTestRemoteURL%" --runTests --dotnetPath "%_CoreFXTestHost%\dotnet.exe" --executable %_CoreFXTestExecutable% --log %_CoreFXLogsDir% --maxProcessCount 5 %_CoreFXTestExecutableArgs%
+if errorlevel 1 (
+ echo %__MsgPrefix%Running CoreFX tests finished with Failures
+ echo %__MsgPrefix%Check %_CoreFXLogsDir% for test run logs
+ exit /b 1
+)
+
)
goto TestsDone
diff --git a/tests/src/Common/CoreFX/TestFileSetup/TestFileSetup.cs b/tests/src/Common/CoreFX/TestFileSetup/Helpers/TestFileHelper.cs
similarity index 68%
rename from tests/src/Common/CoreFX/TestFileSetup/TestFileSetup.cs
rename to tests/src/Common/CoreFX/TestFileSetup/Helpers/TestFileHelper.cs
index 50e89c5ad766..3f4f638e55cc 100644
--- a/tests/src/Common/CoreFX/TestFileSetup/TestFileSetup.cs
+++ b/tests/src/Common/CoreFX/TestFileSetup/Helpers/TestFileHelper.cs
@@ -15,7 +15,7 @@
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
-namespace CoreFX.TestUtils.TestFileSetup
+namespace CoreFX.TestUtils.TestFileSetup.Helpers
{
///
/// Defines the set of flags that represent exit codes
@@ -24,9 +24,10 @@ namespace CoreFX.TestUtils.TestFileSetup
public enum ExitCode : int
{
Success = 0,
- HttpError = 1,
- IOError = 2,
- JsonSchemaValidationError = 3,
+ TestFailure = 1,
+ HttpError = 2,
+ IOError = 3,
+ JsonSchemaValidationError = 4,
UnknownError = 10
}
@@ -35,83 +36,23 @@ public enum ExitCode : int
/// This helper class is used to fetch CoreFX tests from a specified URL, unarchive them and create a flat directory structure
/// through which to iterate.
///
- public static class TestFileSetup
+ public class TestFileHelper
{
- private static HttpClient httpClient;
- private static bool cleanTestBuild = false;
-
- private static string outputDir;
- private static string testUrl;
- private static string testListPath;
-
- public static void Main(string[] args)
+ private HttpClient httpClient;
+ public HttpClient HttpClient
{
- ExitCode exitCode = ExitCode.UnknownError;
- ArgumentSyntax argSyntax = ParseCommandLine(args);
-
- try
+ get
{
- if (!Directory.Exists(outputDir))
- Directory.CreateDirectory(outputDir);
-
- if (cleanTestBuild)
+ if (httpClient == null)
{
- CleanBuild(outputDir);
+ httpClient = new HttpClient();
}
-
- // Map test names to their definitions
- Dictionary testAssemblyDefinitions = DeserializeTestJson(testListPath);
-
- SetupTests(testUrl, outputDir, testAssemblyDefinitions).Wait();
- exitCode = ExitCode.Success;
+ return httpClient;
}
-
- catch (AggregateException e)
- {
- e.Handle(innerExc =>
- {
-
- if (innerExc is HttpRequestException)
- {
- exitCode = ExitCode.HttpError;
- Console.WriteLine("Error downloading tests from: " + testUrl);
- Console.WriteLine(innerExc.Message);
- return true;
- }
- else if (innerExc is IOException)
- {
- exitCode = ExitCode.IOError;
- Console.WriteLine(innerExc.Message);
- return true;
- }
- else if (innerExc is JSchemaValidationException || innerExc is JsonSerializationException)
- {
- exitCode = ExitCode.JsonSchemaValidationError;
- Console.WriteLine("Error validating test list: ");
- Console.WriteLine(innerExc.Message);
- return true;
- }
- return false;
- });
- }
-
- Environment.Exit((int)exitCode);
- }
-
- private static ArgumentSyntax ParseCommandLine(string[] args)
- {
- ArgumentSyntax argSyntax = ArgumentSyntax.Parse(args, syntax =>
- {
- syntax.DefineOption("out|outDir|outputDirectory", ref outputDir, "Directory where tests are downloaded");
- syntax.DefineOption("testUrl", ref testUrl, "URL, pointing to the list of tests");
- syntax.DefineOption("testListJsonPath", ref testListPath, "JSON-formatted list of test assembly names to download");
- syntax.DefineOption("clean|cleanOutputDir", ref cleanTestBuild, "Clean test assembly output directory");
- });
-
- return argSyntax;
+ set{ httpClient = value; }
}
- private static Dictionary DeserializeTestJson(string testDefinitionFilePath)
+ public Dictionary DeserializeTestJson(string testDefinitionFilePath)
{
JSchemaGenerator jsonGenerator = new JSchemaGenerator();
@@ -166,7 +107,7 @@ private static Dictionary DeserializeTestJson(string
return nameToTestAssemblyDef;
}
- private static async Task SetupTests(string jsonUrl, string destinationDirectory, Dictionary testDefinitions = null, bool runAllTests = false)
+ public async Task SetupTests(string jsonUrl, string destinationDirectory, Dictionary testDefinitions = null, bool runAllTests = false)
{
Debug.Assert(Directory.Exists(destinationDirectory));
Debug.Assert(runAllTests || testDefinitions != null);
@@ -195,16 +136,11 @@ private static async Task SetupTests(string jsonUrl, string destinationDirectory
Directory.Delete(tempDirPath);
}
- private static async Task> GetTestUrls(string jsonUrl, Dictionary testDefinitions = null, bool runAllTests = false)
+ public async Task> GetTestUrls(string jsonUrl, Dictionary testDefinitions = null, bool runAllTests = false)
{
- if (httpClient is null)
- {
- httpClient = new HttpClient();
- }
-
Debug.Assert(runAllTests || testDefinitions != null);
// Set up the json stream reader
- using (var responseStream = await httpClient.GetStreamAsync(jsonUrl))
+ using (var responseStream = await HttpClient.GetStreamAsync(jsonUrl))
using (var streamReader = new StreamReader(responseStream))
using (var jsonReader = new JsonTextReader(streamReader))
{
@@ -249,13 +185,8 @@ private static async Task> GetTestUrls(str
return testDefinitions;
}
- private static async Task GetTestArchives(Dictionary testPayloads, string downloadDir)
+ public async Task GetTestArchives(Dictionary testPayloads, string downloadDir)
{
- if (httpClient is null)
- {
- httpClient = new HttpClient();
- }
-
foreach (string testName in testPayloads.Keys)
{
string payloadUri = testPayloads[testName].Url;
@@ -263,7 +194,7 @@ private static async Task GetTestArchives(Dictionary
if (!Uri.IsWellFormedUriString(payloadUri, UriKind.Absolute))
continue;
- using (var response = await httpClient.GetStreamAsync(payloadUri))
+ using (var response = await HttpClient.GetStreamAsync(payloadUri))
{
if (response.CanRead)
{
@@ -287,7 +218,7 @@ private static async Task GetTestArchives(Dictionary
}
}
- private static void ExpandArchivesInDirectory(string archiveDirectory, string destinationDirectory, bool cleanup = true)
+ public void ExpandArchivesInDirectory(string archiveDirectory, string destinationDirectory, bool cleanup = true)
{
Debug.Assert(Directory.Exists(archiveDirectory));
Debug.Assert(Directory.Exists(destinationDirectory));
@@ -309,7 +240,7 @@ private static void ExpandArchivesInDirectory(string archiveDirectory, string de
}
}
- private static void CleanBuild(string directoryToClean)
+ public void CleanBuild(string directoryToClean)
{
Debug.Assert(Directory.Exists(directoryToClean));
DirectoryInfo dirInfo = new DirectoryInfo(directoryToClean);
diff --git a/tests/src/Common/CoreFX/TestFileSetup/Helpers/TestRunHelper.cs b/tests/src/Common/CoreFX/TestFileSetup/Helpers/TestRunHelper.cs
new file mode 100644
index 000000000000..31b7d3454c80
--- /dev/null
+++ b/tests/src/Common/CoreFX/TestFileSetup/Helpers/TestRunHelper.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CoreFX.TestUtils.TestFileSetup.Helpers
+{
+ public class NetCoreTestRunHelper
+ {
+ public string DotnetExecutablePath { get; set; }
+
+ public string logRootOutputPath { get; set; }
+
+ public int TestRunExitCode { get; set; }
+
+
+ public NetCoreTestRunHelper(string DotnetExecutablePath, string logRootOutputPath)
+ {
+ this.DotnetExecutablePath = DotnetExecutablePath;
+ this.logRootOutputPath = logRootOutputPath;
+ }
+
+ public int RunExecutable(string workingDirectory, string executableName, IReadOnlyList xunitTestTraits, string logRootOutputPath)
+ {
+ string logPath = Path.Combine(logRootOutputPath, Path.GetFileName(workingDirectory));
+ if (!Directory.Exists(logPath))
+ Directory.CreateDirectory(logPath);
+ string arguments = CalculateCommandLineArguments(workingDirectory, executableName, xunitTestTraits, Path.Combine(logPath,"testResults.xml"));
+
+ ProcessStartInfo startInfo = new ProcessStartInfo(DotnetExecutablePath, arguments)
+ {
+ Arguments = arguments,
+ WorkingDirectory = workingDirectory
+ };
+
+
+ Process executableProcess = new Process();
+ executableProcess.StartInfo = startInfo;
+ executableProcess.EnableRaisingEvents = true;
+ executableProcess.Exited += new EventHandler(ExitEventHandler);
+ executableProcess.Start();
+ executableProcess.WaitForExit();
+
+ return executableProcess.ExitCode;
+ }
+
+ private void ExitEventHandler(object sender, EventArgs e)
+ {
+ TestRunExitCode = (sender as Process).ExitCode;
+ }
+
+ public int RunAllExecutablesInDirectory(string rootDirectory, string executableName, IReadOnlyList xunitTestTraits, int processLimit, string logRootOutputPath = null)
+ {
+ int result = 0;
+ // Do a Depth-First Search to find and run executables with the same name
+ Stack directories = new Stack();
+ List testDirectories = new List();
+ // Push rootdir
+ directories.Push(rootDirectory);
+
+ while (directories.Count > 0)
+ {
+ string currentDirectory = directories.Pop();
+
+ if (File.Exists(Path.Combine(currentDirectory, executableName)))
+ testDirectories.Add(currentDirectory);
+
+ foreach (string subDir in Directory.GetDirectories(currentDirectory))
+ directories.Push(subDir);
+ }
+
+ ParallelOptions parallelOptions = new ParallelOptions();
+ parallelOptions.MaxDegreeOfParallelism = processLimit;
+
+ Parallel.ForEach(testDirectories, parallelOptions,
+ (testDirectory) =>
+ {
+ if (RunExecutable(testDirectory, executableName, xunitTestTraits, logRootOutputPath) != 0)
+ result = 1;
+ }
+ );
+ return result;
+ }
+
+ private string CalculateCommandLineArguments(string testDirectory, string executableName, IReadOnlyList xunitTestTraits, string logPath)
+ {
+ StringBuilder arguments = new StringBuilder();
+
+ arguments.Append("\"");
+ arguments.Append(Path.Combine(testDirectory, Path.GetFileName(executableName)));
+ arguments.Append("\"");
+ arguments.Append(" ");
+
+ // Append test name dll
+ arguments.Append("\"");
+ arguments.Append(Path.Combine(testDirectory, Path.GetFileName(testDirectory)));
+ arguments.Append(".dll");
+ arguments.Append("\"");
+
+ arguments.Append(" ");
+
+ // Append RSP file
+ arguments.Append("@");
+ arguments.Append("\"");
+ arguments.Append(Path.Combine(testDirectory, Path.GetFileName(testDirectory)));
+ arguments.Append(".rsp");
+ arguments.Append("\"");
+ arguments.Append(" ");
+
+ if (!String.IsNullOrEmpty(logPath))
+ {
+ // Add logging information
+ arguments.Append("-xml");
+ arguments.Append(" ");
+ arguments.Append(logPath);
+ arguments.Append(" ");
+ }
+
+ // Append all additional arguments
+ foreach (string traitToExclude in xunitTestTraits)
+ {
+ arguments.Append("-notrait");
+ arguments.Append(" ");
+ arguments.Append("category=");
+
+ arguments.Append(traitToExclude);
+ arguments.Append(" ");
+ }
+
+
+ return arguments.ToString();
+ }
+ }
+}
diff --git a/tests/src/Common/CoreFX/TestFileSetup/Program.cs b/tests/src/Common/CoreFX/TestFileSetup/Program.cs
new file mode 100644
index 000000000000..32a5ed9b8d72
--- /dev/null
+++ b/tests/src/Common/CoreFX/TestFileSetup/Program.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using CoreFX.TestUtils.TestFileSetup.Helpers;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Schema;
+
+namespace CoreFX.TestUtils.TestFileSetup
+{
+ public class Program
+ {
+ private static TestFileHelper testFileHelper;
+ private static NetCoreTestRunHelper testRunHelper;
+
+ // Test Set-up Options
+ private static string outputDir;
+ private static string testUrl;
+ private static string testListPath;
+ private static bool cleanTestBuild = false;
+
+ // Test Run Options
+ private static string dotnetPath;
+ private static bool runTests = false;
+ private static int maximumDegreeOfParalellization;
+ private static string logRootOutputPath;
+
+ private static ExitCode exitCode;
+ private static string executableName;
+ private static IReadOnlyList traitExclusions = Array.Empty();
+
+ public static void Main(string[] args)
+ {
+ exitCode = ExitCode.Success;
+ ArgumentSyntax argSyntax = ParseCommandLine(args);
+ try
+ {
+ SetupTests();
+
+ if (runTests)
+ {
+ if (String.IsNullOrEmpty(dotnetPath))
+ throw new ArgumentException("Please supply a test host location to run tests.");
+
+ if (!File.Exists(dotnetPath))
+ throw new ArgumentException("Invalid testhost path. Please supply a test host location to run tests.");
+
+ exitCode = RunTests();
+
+ }
+ }
+ catch (AggregateException e)
+ {
+ e.Handle(innerExc =>
+ {
+
+ if (innerExc is HttpRequestException)
+ {
+ exitCode = ExitCode.HttpError;
+ Console.WriteLine("Error downloading tests from: " + testUrl);
+ Console.WriteLine(innerExc.Message);
+ return true;
+ }
+ else if (innerExc is IOException)
+ {
+ exitCode = ExitCode.IOError;
+ Console.WriteLine(innerExc.Message);
+ return true;
+ }
+ else if (innerExc is JSchemaValidationException || innerExc is JsonSerializationException)
+ {
+ exitCode = ExitCode.JsonSchemaValidationError;
+ Console.WriteLine("Error validating test list: ");
+ Console.WriteLine(innerExc.Message);
+ return true;
+ }
+ else
+ {
+ exitCode = ExitCode.UnknownError;
+ }
+ return false;
+ });
+ }
+
+ Environment.Exit((int)exitCode);
+ }
+
+
+ private static ArgumentSyntax ParseCommandLine(string[] args)
+ {
+ ArgumentSyntax argSyntax = ArgumentSyntax.Parse(args, syntax =>
+ {
+ syntax.DefineOption("out|outDir|outputDirectory", ref outputDir, "Directory where tests are downloaded");
+ syntax.DefineOption("testUrl", ref testUrl, "URL, pointing to the list of tests");
+ syntax.DefineOption("testListJsonPath", ref testListPath, "JSON-formatted list of test assembly names to download");
+ syntax.DefineOption("clean|cleanOutputDir", ref cleanTestBuild, "Clean test assembly output directory");
+ syntax.DefineOption("run|runTests", ref runTests, "Run Tests after setup");
+ syntax.DefineOption("dotnet|dotnetPath", ref dotnetPath, "Path to dotnet executable used to run tests.");
+ syntax.DefineOption("executable|executableName", ref executableName, "Name of the test executable to start");
+ syntax.DefineOption("log|logPath|logRootOutputPath", ref logRootOutputPath, "Run Tests after setup");
+ syntax.DefineOption("maxProcessCount|numberOfParallelTests|maximumDegreeOfParalellization", ref maximumDegreeOfParalellization, "Maximum number of concurrently executing processes");
+ syntax.DefineOptionList("notrait", ref traitExclusions, "Traits to be excluded from test runs");
+
+ });
+ return argSyntax;
+ }
+
+ private static void SetupTests()
+ {
+ testFileHelper = new TestFileHelper();
+
+ if (!Directory.Exists(outputDir))
+ Directory.CreateDirectory(outputDir);
+
+ if (cleanTestBuild)
+ {
+ testFileHelper.CleanBuild(outputDir);
+ }
+
+ // Map test names to their definitions
+ Dictionary testAssemblyDefinitions = testFileHelper.DeserializeTestJson(testListPath);
+
+ testFileHelper.SetupTests(testUrl, outputDir, testAssemblyDefinitions).Wait();
+ }
+
+ private static ExitCode RunTests()
+ {
+ testRunHelper = new NetCoreTestRunHelper(dotnetPath, logRootOutputPath);
+ int result = testRunHelper.RunAllExecutablesInDirectory(outputDir, executableName, traitExclusions, maximumDegreeOfParalellization, logRootOutputPath);
+
+ return result == 0 ? ExitCode.Success : ExitCode.TestFailure;
+ }
+ }
+}
diff --git a/tests/src/Common/CoreFX/TestFileSetup/RSPGenerator.cs b/tests/src/Common/CoreFX/TestFileSetup/RSPGenerator.cs
index f67dd424186b..444c7dfeb19b 100644
--- a/tests/src/Common/CoreFX/TestFileSetup/RSPGenerator.cs
+++ b/tests/src/Common/CoreFX/TestFileSetup/RSPGenerator.cs
@@ -7,6 +7,8 @@ namespace CoreFX.TestUtils.TestFileSetup
{
public class RSPGenerator
{
+ private const int MAX_THREAD_NUM = 10;
+
public void GenerateRSPFile(XUnitTestAssembly testDefinition, string outputPath)
{
if (!Directory.Exists(outputPath))
diff --git a/tests/src/Common/CoreFX/TestFileSetup/XUnitTestAssembly.cs b/tests/src/Common/CoreFX/TestFileSetup/XUnit/XUnitTestAssembly.cs
similarity index 100%
rename from tests/src/Common/CoreFX/TestFileSetup/XUnitTestAssembly.cs
rename to tests/src/Common/CoreFX/TestFileSetup/XUnit/XUnitTestAssembly.cs