diff --git a/tests/src/tools/ReadyToRun.TestHarness/PathHelpers.cs b/tests/src/tools/ReadyToRun.TestHarness/PathHelpers.cs
new file mode 100644
index 000000000..155ca2623
--- /dev/null
+++ b/tests/src/tools/ReadyToRun.TestHarness/PathHelpers.cs
@@ -0,0 +1,34 @@
+// 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;
+
+namespace ReadyToRun.TestHarness
+{
+ ///
+ /// A set of helper to manipulate paths into a canonicalized form to ensure user-provided paths
+ /// match those in the ETW log.
+ ///
+ static class PathExtensions
+ {
+ internal static string ToAbsolutePath(this string argValue) => Path.GetFullPath(argValue);
+
+ internal static string ToAbsoluteDirectoryPath(this string argValue) => argValue.ToAbsolutePath().StripTrailingDirectorySeparators();
+
+ internal static string StripTrailingDirectorySeparators(this string str)
+ {
+ if (String.IsNullOrWhiteSpace(str))
+ {
+ return str;
+ }
+
+ while (str.Length > 0 && str[str.Length - 1] == Path.DirectorySeparatorChar)
+ {
+ str = str.Remove(str.Length - 1);
+ }
+
+ return str;
+ }
+ }
+}
diff --git a/tests/src/tools/ReadyToRun.TestHarness/Program.cs b/tests/src/tools/ReadyToRun.TestHarness/Program.cs
index d2d066c55..6ffe766f1 100644
--- a/tests/src/tools/ReadyToRun.TestHarness/Program.cs
+++ b/tests/src/tools/ReadyToRun.TestHarness/Program.cs
@@ -39,13 +39,14 @@ namespace ReadyToRun.TestHarness
private static string _coreRunExePath;
private static string _testExe;
private static IReadOnlyList _referenceFilenames = Array.Empty();
+ private static IReadOnlyList _referenceFolders = Array.Empty();
private static string _whitelistFilename;
private static IReadOnlyList _testargs = Array.Empty();
private static bool _noEtl;
static void ShowUsage()
{
- Console.WriteLine("dotnet ReadyToRun.TestHarness --corerun --in --ref [ReferencedBinaries] --whitelist [MethodWhiteListFile] --testargs [TestArgs]");
+ Console.WriteLine("dotnet ReadyToRun.TestHarness --corerun --in --ref [ReferencedBinaries] --whitelist [MethodWhiteListFile] --testargs [TestArgs] --include [FoldersContainingAssemblies]");
}
private static ArgumentSyntax ParseCommandLine(string[] args)
@@ -55,11 +56,13 @@ namespace ReadyToRun.TestHarness
syntax.ApplicationName = "ReadyToRun.TestHarness";
syntax.HandleHelp = false;
syntax.HandleErrors = true;
+ syntax.HandleResponseFiles = true;
syntax.DefineOption("h|help", ref _help, "Help message for R2RDump");
syntax.DefineOption("c|corerun", ref _coreRunExePath, "Path to CoreRun");
syntax.DefineOption("i|in", ref _testExe, "Path to test exe");
syntax.DefineOptionList("r|ref", ref _referenceFilenames, "Paths to referenced assemblies");
+ syntax.DefineOptionList("include", ref _referenceFolders, "Folders containing assemblies to monitor");
syntax.DefineOption("w|whitelist", ref _whitelistFilename, "Path to method whitelist file");
syntax.DefineOptionList("testargs", ref _testargs, "Args to pass into test");
syntax.DefineOption ("noetl", ref _noEtl, "Run the test without ETL enabled");
@@ -100,24 +103,39 @@ namespace ReadyToRun.TestHarness
}
var testModules = new HashSet();
- testModules.Add(_testExe);
+ var testFolders = new HashSet();
+ testModules.Add(_testExe.ToLower());
foreach (string reference in _referenceFilenames)
{
- testModules.Add(reference);
+ // CoreCLR generates ETW events with all lower case native image files that break our string comparison.
+ testModules.Add(reference.ToLower());
+ }
+
+ foreach (string reference in _referenceFolders)
+ {
+ string absolutePath = reference.ToAbsoluteDirectoryPath();
+
+ if (!Directory.Exists(reference))
+ {
+ Console.WriteLine($"Error: {reference} does not exist.");
+ ShowUsage();
+ return exitCode;
+ }
+ testFolders.Add(reference.ToLower());
}
if (_noEtl)
{
- RunTest(null, passThroughArguments, out exitCode);
+ RunTest(null, null, passThroughArguments, out exitCode);
}
else
{
using (var session = new TraceEventSession("ReadyToRunTestSession"))
{
- var r2rMethodFilter = new ReadyToRunJittedMethods(session, testModules);
+ var r2rMethodFilter = new ReadyToRunJittedMethods(session, testModules, testFolders);
session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.Loader));
- Task.Run(() => RunTest(session, passThroughArguments, out exitCode));
+ Task.Run(() => RunTest(session, r2rMethodFilter, passThroughArguments, out exitCode));
// Block, processing callbacks for events we subscribed to
session.Source.Process();
@@ -183,9 +201,10 @@ namespace ReadyToRun.TestHarness
int jittedMethodCount = 0;
foreach (var jittedMethod in jittedMethods.JittedMethods)
{
- if (!whiteListedMethods.Contains(jittedMethod.MethodName))
+ string fullName = GetFullModuleName(jittedMethod.assemblyName, jittedMethod.MethodName);
+ if (!whiteListedMethods.Contains(jittedMethod.MethodName) && !whiteListedMethods.Contains(fullName))
{
- Console.WriteLine(jittedMethod.MethodName);
+ Console.WriteLine(fullName);
++jittedMethodCount;
}
}
@@ -203,7 +222,12 @@ namespace ReadyToRun.TestHarness
return StatusTestPassed;
}
- private static void RunTest(TraceEventSession session, string testArguments, out int exitCode)
+ private static string GetFullModuleName(string moduleName, string methodName)
+ {
+ return $"[{moduleName}]{methodName}";
+ }
+
+ private static void RunTest(TraceEventSession session, ReadyToRunJittedMethods r2rMethodFilter, string testArguments, out int exitCode)
{
exitCode = -100;
@@ -217,6 +241,11 @@ namespace ReadyToRun.TestHarness
process.Start();
+ if (r2rMethodFilter != null)
+ {
+ r2rMethodFilter.SetProcessId(process.Id);
+ }
+
process.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args)
{
Console.WriteLine(args.Data);
diff --git a/tests/src/tools/ReadyToRun.TestHarness/ReadyToRunEtlMethodFilter.cs b/tests/src/tools/ReadyToRun.TestHarness/ReadyToRunEtlMethodFilter.cs
index 7fd8dd75e..9a0010f50 100644
--- a/tests/src/tools/ReadyToRun.TestHarness/ReadyToRunEtlMethodFilter.cs
+++ b/tests/src/tools/ReadyToRun.TestHarness/ReadyToRunEtlMethodFilter.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.IO;
using System.Collections.Generic;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
@@ -19,33 +20,61 @@ namespace ReadyToRun.TestHarness
class ReadyToRunJittedMethods
{
private ICollection _testModuleNames;
+ private ICollection _testFolderNames;
private List _testModuleIds = new List();
- private List<(string, bool)> _methodsJitted = new List<(string name, bool readyToRunRejected)>();
+ private Dictionary _testModuleIdToName = new Dictionary();
+ private List<(string, string, bool)> _methodsJitted = new List<(string name, string moduleName, bool readyToRunRejected)>();
+ private int _pid = -1;
- public ReadyToRunJittedMethods(TraceEventSession session, ICollection testModuleNames)
+ public ReadyToRunJittedMethods(TraceEventSession session, ICollection testModuleNames, ICollection testFolderNames)
{
_testModuleNames = testModuleNames;
+ _testFolderNames = testFolderNames;
session.Source.Clr.LoaderModuleLoad += delegate(ModuleLoadUnloadTraceData data)
{
- if (_testModuleNames.Contains(data.ModuleILPath) || _testModuleNames.Contains(data.ModuleNativePath))
+ if (ShouldMonitorModule(data))
{
Console.WriteLine($"Tracking module {data.ModuleILFileName} with Id {data.ModuleID}");
_testModuleIds.Add(data.ModuleID);
+ _testModuleIdToName[data.ModuleID] = Path.GetFileNameWithoutExtension(data.ModuleILFileName);
}
};
session.Source.Clr.MethodLoadVerbose += delegate (MethodLoadUnloadVerboseTraceData data)
{
- if (_testModuleIds.Contains(data.ModuleID) && data.IsJitted)
+ if (data.ProcessID == _pid && _testModuleIds.Contains(data.ModuleID) && data.IsJitted)
{
Console.WriteLine($"Method loaded {GetName(data)} - {data}");
- _methodsJitted.Add((GetName(data), ((int)data.MethodFlags & 0x40) != 0));
+ _methodsJitted.Add((GetName(data), _testModuleIdToName[data.ModuleID], ((int)data.MethodFlags & 0x40) != 0));
}
};
}
- public IEnumerable<(string MethodName, bool ReadyToRunRejected)> JittedMethods => _methodsJitted;
+ private bool ShouldMonitorModule(ModuleLoadUnloadTraceData data)
+ {
+ if (data.ProcessID != _pid)
+ return false;
+
+ if (File.Exists(data.ModuleILPath) && _testFolderNames.Contains(Path.GetDirectoryName(data.ModuleILPath).ToAbsoluteDirectoryPath().ToLower()))
+ return true;
+
+ if (_testModuleNames.Contains(data.ModuleILPath.ToLower()) || _testModuleNames.Contains(data.ModuleNativePath.ToLower()))
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Set the process to monitor events for given its Id. This should be set immediately after
+ /// calling Process.Start to ensure no module load events are missed for the runtime instance.
+ ///
+ public void SetProcessId(int pid)
+ {
+ _pid = pid;
+ }
+
+ public IEnumerable<(string MethodName, string assemblyName, bool ReadyToRunRejected)> JittedMethods => _methodsJitted;
///
/// Returns the number of test assemblies that were loaded by the runtime