diff --git a/eng/pipelines/common/templates/runtimes/build-test-job.yml b/eng/pipelines/common/templates/runtimes/build-test-job.yml index b3b62e96256e5c..5faf51570288f4 100644 --- a/eng/pipelines/common/templates/runtimes/build-test-job.yml +++ b/eng/pipelines/common/templates/runtimes/build-test-job.yml @@ -111,7 +111,7 @@ jobs: displayName: Disk Usage before Build # Build managed test components - - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) $(logRootNameArg)Managed allTargets skipnative skipgeneratelayout skiptestwrappers $(buildConfig) $(archType) $(runtimeFlavorArgs) $(crossArg) $(priorityArg) $(testFilterArg) ci /p:TargetOS=AnyOS + - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) $(logRootNameArg)Managed allTargets skipnative skipgeneratelayout $(buildConfig) $(archType) $(runtimeFlavorArgs) $(crossArg) $(priorityArg) $(testFilterArg) ci /p:TargetOS=AnyOS displayName: Build managed test components - ${{ if in(parameters.osGroup, 'osx', 'ios', 'tvos') }}: diff --git a/eng/pipelines/common/templates/runtimes/run-test-job.yml b/eng/pipelines/common/templates/runtimes/run-test-job.yml index 44a5d48a7b478e..b521530b76836a 100644 --- a/eng/pipelines/common/templates/runtimes/run-test-job.yml +++ b/eng/pipelines/common/templates/runtimes/run-test-job.yml @@ -236,12 +236,6 @@ jobs: - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) copynativeonly $(logRootNameArg)Native $(testFilterArg) $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(priorityArg) $(librariesOverrideArg) $(codeFlowEnforcementArg) displayName: Copy native test components to test output folder - - # Generate test wrappers. This is the step that examines issues.targets to exclude tests. - - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) buildtestwrappersonly $(logRootNameArg)Wrappers $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(crossArg) $(priorityArg) $(librariesOverrideArg) $(runtimeVariantArg) - displayName: Generate test wrappers - - # Compose the Core_Root folder containing all artifacts needed for running # CoreCLR tests. This step also compiles the framework using Crossgen2 # in ReadyToRun jobs. diff --git a/src/tests/Common/CLRTest.CrossGen.targets b/src/tests/Common/CLRTest.CrossGen.targets index 6fdd2000655c7e..8b45eb30ddec36 100644 --- a/src/tests/Common/CLRTest.CrossGen.targets +++ b/src/tests/Common/CLRTest.CrossGen.targets @@ -61,8 +61,6 @@ if [ ! -z ${RunCrossGen2+x} ]%3B then mkdir IL-CG2 cp ./*.dll IL-CG2/ rm IL-CG2/composite-r2r.* 2>/dev/null - rm IL-CG2/Coreclr.TestWrapper.dll 2>/dev/null - rm IL-CG2/*.XUnitWrapper.dll 2>/dev/null if [ ! -z ${CompositeBuildMode+x} ]%3B then # HACK: copy native shared shim libraries. Not needed on Windows. @@ -254,8 +252,6 @@ if defined RunCrossGen2 ( mkdir IL-CG2 copy *.dll IL-CG2\ del IL-CG2\composite-r2r.* 2>nul - del IL-CG2\Coreclr.TestWrapper.dll 2>nul - del IL-CG2\*.XUnitWrapper.dll 2>nul if defined CompositeBuildMode ( set ExtraCrossGen2Args=!ExtraCrossGen2Args! --composite diff --git a/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj b/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj index 6bbebee74d7177..e3232266bc90af 100644 --- a/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj +++ b/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj @@ -7,6 +7,7 @@ + @@ -19,8 +20,6 @@ - - diff --git a/src/tests/Common/CoreCLRTestLibrary/CoreclrTestWrapperLib.cs b/src/tests/Common/CoreCLRTestLibrary/CoreclrTestWrapperLib.cs new file mode 100644 index 00000000000000..d09282fc976844 --- /dev/null +++ b/src/tests/Common/CoreCLRTestLibrary/CoreclrTestWrapperLib.cs @@ -0,0 +1,579 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; + +namespace TestLibrary +{ + static class DbgHelp + { + public enum MiniDumpType : int + { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + MiniDumpWithPrivateWriteCopyMemory = 0x00010000, + MiniDumpIgnoreInaccessibleMemory = 0x00020000, + MiniDumpWithTokenInformation = 0x00040000, + MiniDumpWithModuleHeaders = 0x00080000, + MiniDumpFilterTriage = 0x00100000, + MiniDumpValidTypeFlags = 0x001fffff + } + + [DllImport("DbgHelp.dll", SetLastError = true)] + public static extern bool MiniDumpWriteDump(IntPtr handle, int processId, SafeFileHandle file, MiniDumpType dumpType, IntPtr exceptionParam, IntPtr userStreamParam, IntPtr callbackParam); + } + + static class Kernel32 + { + public const int MAX_PATH = 260; + public const int ERROR_NO_MORE_FILES = 0x12; + public const long INVALID_HANDLE = -1; + + public enum Toolhelp32Flags : uint + { + TH32CS_INHERIT = 0x80000000, + TH32CS_SNAPHEAPLIST = 0x00000001, + TH32CS_SNAPMODULE = 0x00000008, + TH32CS_SNAPMODULE32 = 0x00000010, + TH32CS_SNAPPROCESS = 0x00000002, + TH32CS_SNAPTHREAD = 0x00000004 + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public unsafe struct ProcessEntry32W + { + public int Size; + public int Usage; + public int ProcessID; + public IntPtr DefaultHeapID; + public int ModuleID; + public int Threads; + public int ParentProcessID; + public int PriClassBase; + public int Flags; + public fixed char ExeFile[MAX_PATH]; + } + + [DllImport("kernel32.dll")] + public static extern bool CloseHandle(IntPtr handle); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateToolhelp32Snapshot(Toolhelp32Flags flags, int processId); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool Process32FirstW(IntPtr snapshot, ref ProcessEntry32W entry); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool Process32NextW(IntPtr snapshot, ref ProcessEntry32W entry); + } + + static class @libproc + { + [DllImport(nameof(libproc))] + private static extern int proc_listchildpids(int ppid, int[]? buffer, int byteSize); + + public static unsafe bool ListChildPids(int ppid, out int[] buffer) + { + int n = proc_listchildpids(ppid, null, 0); + buffer = new int[n]; + return proc_listchildpids(ppid, buffer, buffer.Length * sizeof(int)) != -1; + } + } + + internal static class ProcessExtensions + { + public static bool TryGetProcessId(this Process process, out int processId) + { + try + { + processId = process.Id; + return true; + } + catch + { + // Process exited + processId = default; + return false; + } + } + + public static bool TryGetProcessName(this Process process, out string processName) + { + try + { + processName = process.ProcessName; + return true; + } + catch + { + // Process exited + processName = default; + return false; + } + } + + public unsafe static IEnumerable GetChildren(this Process process) + { + var children = new List(); + if (OperatingSystem.IsWindows()) + { + return Windows_GetChildren(process); + } + else if (OperatingSystem.IsLinux()) + { + return Linux_GetChildren(process); + } + else if (OperatingSystem.IsMacOS()) + { + return MacOS_GetChildren(process); + } + return children; + } + + private unsafe static IEnumerable Windows_GetChildren(Process process) + { + var children = new List(); + IntPtr snapshot = Kernel32.CreateToolhelp32Snapshot(Kernel32.Toolhelp32Flags.TH32CS_SNAPPROCESS, 0); + if (snapshot != IntPtr.Zero && snapshot.ToInt64() != Kernel32.INVALID_HANDLE) + { + try + { + children = new List(); + int ppid = process.Id; + + var processEntry = new Kernel32.ProcessEntry32W { Size = sizeof(Kernel32.ProcessEntry32W) }; + + bool success = Kernel32.Process32FirstW(snapshot, ref processEntry); + while (success) + { + if (processEntry.ParentProcessID == ppid) + { + try + { + children.Add(Process.GetProcessById(processEntry.ProcessID)); + } + catch {} + } + + success = Kernel32.Process32NextW(snapshot, ref processEntry); + } + + } + finally + { + Kernel32.CloseHandle(snapshot); + } + } + + return children; + } + + private static IEnumerable Linux_GetChildren(Process process) + { + var children = new List(); + List? childPids = null; + + try + { + childPids = File.ReadAllText($"/proc/{process.Id}/task/{process.Id}/children") + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Select(pidString => int.Parse(pidString)) + .ToList(); + } + catch (IOException e) + { + // Some distros might not have the /proc/pid/task/tid/children entry enabled in the kernel + // attempt to use pgrep then + var pgrepInfo = new ProcessStartInfo("pgrep"); + pgrepInfo.RedirectStandardOutput = true; + pgrepInfo.Arguments = $"-P {process.Id}"; + + using Process pgrep = Process.Start(pgrepInfo)!; + + string[] pidStrings = pgrep.StandardOutput.ReadToEnd().Split('\n', StringSplitOptions.RemoveEmptyEntries); + pgrep.WaitForExit(); + + childPids = new List(); + foreach (var pidString in pidStrings) + if (int.TryParse(pidString, out int childPid)) + childPids.Add(childPid); + } + + foreach (var pid in childPids) + { + try + { + children.Add(Process.GetProcessById(pid)); + } + catch (ArgumentException) + { + // Ignore failure to get process, the process may have exited + } + } + + return children; + } + + private static IEnumerable MacOS_GetChildren(Process process) + { + var children = new List(); + if (libproc.ListChildPids(process.Id, out int[] childPids)) + { + foreach (var childPid in childPids) + { + children.Add(Process.GetProcessById(childPid)); + } + } + + return children; + } + } + + internal class CoreclrTestWrapperLib + { + public const int EXIT_SUCCESS_CODE = 0; + public const string TIMEOUT_ENVIRONMENT_VAR = "__TestTimeout"; + + // Default timeout set to 10 minutes + public const int DEFAULT_TIMEOUT_MS = 1000 * 60 * 10; + + public const string COLLECT_DUMPS_ENVIRONMENT_VAR = "__CollectDumps"; + public const string CRASH_DUMP_FOLDER_ENVIRONMENT_VAR = "__CrashDumpFolder"; + + public const string TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR = "__TestArchitecture"; + + static bool CollectCrashDump(Process process, string crashDumpPath, StreamWriter outputWriter) + { + if (OperatingSystem.IsWindows()) + { + return CollectCrashDumpWithMiniDumpWriteDump(process, crashDumpPath, outputWriter); + } + else + { + return CollectCrashDumpWithCreateDump(process, crashDumpPath, outputWriter); + } + } + + static bool CollectCrashDumpWithMiniDumpWriteDump(Process process, string crashDumpPath, StreamWriter outputWriter) + { + bool collectedDump = false; + using (var crashDump = File.OpenWrite(crashDumpPath)) + { + var flags = DbgHelp.MiniDumpType.MiniDumpWithFullMemory | DbgHelp.MiniDumpType.MiniDumpIgnoreInaccessibleMemory; + collectedDump = DbgHelp.MiniDumpWriteDump(process.Handle, process.Id, crashDump.SafeFileHandle, flags, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + return collectedDump; + } + + static bool CollectCrashDumpWithCreateDump(Process process, string crashDumpPath, StreamWriter outputWriter) + { + string? coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); + if (coreRoot is null) + { + throw new InvalidOperationException("CORE_ROOT environment variable is not set."); + } + string createdumpPath = Path.Combine(coreRoot, "createdump"); + string arguments = $"--crashreport --name \"{crashDumpPath}\" {process.Id} --withheap"; + Process createdump = new Process(); + + createdump.StartInfo.FileName = "sudo"; + createdump.StartInfo.Arguments = $"{createdumpPath} {arguments}"; + + createdump.StartInfo.UseShellExecute = false; + createdump.StartInfo.RedirectStandardOutput = true; + createdump.StartInfo.RedirectStandardError = true; + + Console.WriteLine($"Invoking: {createdump.StartInfo.FileName} {createdump.StartInfo.Arguments}"); + createdump.Start(); + + Task copyOutput = createdump.StandardOutput.ReadToEndAsync(); + Task copyError = createdump.StandardError.ReadToEndAsync(); + bool fSuccess = createdump.WaitForExit(DEFAULT_TIMEOUT_MS); + + if (fSuccess) + { + Task.WaitAll(copyError, copyOutput); + string output = copyOutput.Result; + string error = copyError.Result; + + Console.WriteLine("createdump stdout:"); + Console.WriteLine(output); + Console.WriteLine("createdump stderr:"); + Console.WriteLine(error); + + // Ensure the dump is accessible by current user + Process chown = new Process(); + chown.StartInfo.FileName = "sudo"; + chown.StartInfo.Arguments = $"chown \"{Environment.UserName}\" \"{crashDumpPath}\""; + + chown.StartInfo.UseShellExecute = false; + chown.StartInfo.RedirectStandardOutput = true; + chown.StartInfo.RedirectStandardError = true; + + Console.WriteLine($"Invoking: {chown.StartInfo.FileName} {chown.StartInfo.Arguments}"); + chown.Start(); + copyOutput = chown.StandardOutput.ReadToEndAsync(); + copyError = chown.StandardError.ReadToEndAsync(); + + chown.WaitForExit(DEFAULT_TIMEOUT_MS); + + Task.WaitAll(copyError, copyOutput); + Console.WriteLine("chown stdout:"); + Console.WriteLine(copyOutput.Result); + Console.WriteLine("chown stderr:"); + Console.WriteLine(copyError.Result); + } + else + { + // Workaround for https://github.com/dotnet/runtime/issues/93321 + const int MaxRetries = 5; + for (int i = 0; i < MaxRetries; i++) + { + try + { + createdump.Kill(entireProcessTree: true); + break; + } + catch (Exception e) when (i < MaxRetries - 1) + { + Console.WriteLine($"Process.Kill(entireProcessTree: true) failed:"); + Console.WriteLine(e); + Console.WriteLine("Retrying..."); + } + } + } + + return fSuccess && createdump.ExitCode == 0; + } + + // Finds all children processes starting with a process named childName + // The children are sorted in the order they should be dumped + static unsafe IEnumerable FindChildProcessesByName(Process process, string childName) + { + process.TryGetProcessName(out string parentProcessName); + process.TryGetProcessId(out int parentProcessId); + Console.WriteLine($"Finding all child processes of '{parentProcessName}' (ID: {parentProcessId}) with name '{childName}'"); + + var children = new Stack(); + Queue childrenToCheck = new Queue(); + HashSet seen = new HashSet(); + + seen.Add(parentProcessId); + + try + { + foreach (var child in process.GetChildren()) + childrenToCheck.Enqueue(child); + } + catch + { + // Process exited + } + + while (childrenToCheck.Count != 0) + { + Process child = childrenToCheck.Dequeue(); + + if (!child.TryGetProcessId(out int processId)) + continue; + + if (seen.Contains(processId)) + continue; + + if (!child.TryGetProcessName(out string processName)) + continue; + + Console.WriteLine($"Checking child process: '{processName}' (ID: {processId})"); + seen.Add(processId); + + try + { + foreach (var grandchild in child.GetChildren()) + childrenToCheck.Enqueue(grandchild); + } + catch + { + // Process exited + } + + if (processName.Equals(childName, StringComparison.OrdinalIgnoreCase)) + { + children.Push(child); + } + } + + return children; + } + + public int RunTest(string executable, string outputFile, string errorFile, string category, string testBinaryBase, string outputDir) + { + Debug.Assert(outputFile != errorFile); + + int exitCode = -100; + + // If a timeout was given to us by an environment variable, use it instead of the default + // timeout. + string? environmentVar = Environment.GetEnvironmentVariable(TIMEOUT_ENVIRONMENT_VAR); + int timeout = environmentVar != null ? int.Parse(environmentVar) : DEFAULT_TIMEOUT_MS; + bool collectCrashDumps = Environment.GetEnvironmentVariable(COLLECT_DUMPS_ENVIRONMENT_VAR) != null; + string? crashDumpFolder = Environment.GetEnvironmentVariable(CRASH_DUMP_FOLDER_ENVIRONMENT_VAR); + + var outputStream = new FileStream(outputFile, FileMode.Create); + var errorStream = new FileStream(errorFile, FileMode.Create); + + using (var outputWriter = new StreamWriter(outputStream)) + using (var errorWriter = new StreamWriter(errorStream)) + using (Process process = new Process()) + { + // Windows can run the executable implicitly + if (OperatingSystem.IsWindows()) + { + process.StartInfo.FileName = executable; + } + // Non-windows needs to be told explicitly to run through /bin/bash shell + else + { + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = executable; + } + + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.EnvironmentVariables.Add("__Category", category); + process.StartInfo.EnvironmentVariables.Add("__TestBinaryBase", testBinaryBase); + process.StartInfo.EnvironmentVariables.Add("__OutputDir", outputDir); + + DateTime startTime = DateTime.Now; + process.Start(); + + var cts = new CancellationTokenSource(); + Task copyOutput = process.StandardOutput.BaseStream.CopyToAsync(outputStream, 4096, cts.Token); + Task copyError = process.StandardError.BaseStream.CopyToAsync(errorStream, 4096, cts.Token); + + if (process.WaitForExit(timeout)) + { + // Process completed. Check process.ExitCode here. + exitCode = process.ExitCode; + Task.WaitAll(copyOutput, copyError); + } + else + { + // Timed out. + DateTime endTime = DateTime.Now; + + try + { + cts.Cancel(); + } + catch { } + + outputWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}{2}{3}, start: {4}, end: {5})", + executable, timeout, (environmentVar != null) ? " from variable " : "", (environmentVar != null) ? TIMEOUT_ENVIRONMENT_VAR : "", + startTime.ToString(), endTime.ToString()); + outputWriter.Flush(); + errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}{2}{3}, start: {4}, end: {5})", + executable, timeout, (environmentVar != null) ? " from variable " : "", (environmentVar != null) ? TIMEOUT_ENVIRONMENT_VAR : "", + startTime.ToString(), endTime.ToString()); + errorWriter.Flush(); + + Console.WriteLine("Collecting diagnostic information..."); + Console.WriteLine("Snapshot of processes currently running:"); + Console.WriteLine($"\t{"ID",-6} ProcessName"); + foreach (var activeProcess in Process.GetProcesses()) + { + activeProcess.TryGetProcessName(out string activeProcessName); + activeProcess.TryGetProcessId(out int activeProcessId); + Console.WriteLine($"\t{activeProcessId,-6} {activeProcessName}"); + } + + if (OperatingSystem.IsWindows()) + { + Console.WriteLine("Snapshot of processes currently running (using wmic):"); + Console.WriteLine(GetAllProcessNames_wmic()); + } + + if (collectCrashDumps) + { + if (crashDumpFolder != null) + { + foreach (var child in FindChildProcessesByName(process, "corerun")) + { + string crashDumpPath = Path.Combine(Path.GetFullPath(crashDumpFolder), string.Format("crashdump_{0}.dmp", child.Id)); + Console.WriteLine($"Attempting to collect crash dump: {crashDumpPath}"); + if (CollectCrashDump(child, crashDumpPath, outputWriter)) + { + Console.WriteLine("Collected crash dump: {0}", crashDumpPath); + } + else + { + Console.WriteLine("Failed to collect crash dump"); + } + } + } + } + + // kill the timed out processes after we've collected dumps + process.Kill(entireProcessTree: true); + } + + outputWriter.WriteLine("Test Harness Exitcode is : " + exitCode.ToString()); + outputWriter.Flush(); + errorWriter.Flush(); + } + + return exitCode; + } + + private static string GetAllProcessNames_wmic() + { + // The command to execute + string command = "wmic process get Name, ProcessId, ParentProcessId"; + + // Start the process and capture the output + Process process = new Process(); + process.StartInfo.FileName = "cmd.exe"; + process.StartInfo.Arguments = $"/c {command}"; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + // Start the process and read the output + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(100); // wait for 100 ms + + // Output the result + return output; + } + } +} diff --git a/src/tests/Common/CoreCLRTestLibrary/OutOfProcessTest.cs b/src/tests/Common/CoreCLRTestLibrary/OutOfProcessTest.cs index 2084bd02d96607..e08088951f19bd 100644 --- a/src/tests/Common/CoreCLRTestLibrary/OutOfProcessTest.cs +++ b/src/tests/Common/CoreCLRTestLibrary/OutOfProcessTest.cs @@ -10,7 +10,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using CoreclrTestLib; using Xunit; namespace TestLibrary diff --git a/src/tests/Common/Coreclr.TestWrapper/Coreclr.TestWrapper.csproj b/src/tests/Common/Coreclr.TestWrapper/Coreclr.TestWrapper.csproj deleted file mode 100644 index 30d98c3c9c0fef..00000000000000 --- a/src/tests/Common/Coreclr.TestWrapper/Coreclr.TestWrapper.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - true - $(NetCoreAppCurrentToolTargetFrameworkMoniker) - $(NetCoreAppToolCurrent) - enable - true - true - true - - - - - - - - diff --git a/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs b/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs deleted file mode 100644 index 16320ca83be53c..00000000000000 --- a/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs +++ /dev/null @@ -1,960 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; - -namespace CoreclrTestLib -{ - static class DbgHelp - { - public enum MiniDumpType : int - { - MiniDumpNormal = 0x00000000, - MiniDumpWithDataSegs = 0x00000001, - MiniDumpWithFullMemory = 0x00000002, - MiniDumpWithHandleData = 0x00000004, - MiniDumpFilterMemory = 0x00000008, - MiniDumpScanMemory = 0x00000010, - MiniDumpWithUnloadedModules = 0x00000020, - MiniDumpWithIndirectlyReferencedMemory = 0x00000040, - MiniDumpFilterModulePaths = 0x00000080, - MiniDumpWithProcessThreadData = 0x00000100, - MiniDumpWithPrivateReadWriteMemory = 0x00000200, - MiniDumpWithoutOptionalData = 0x00000400, - MiniDumpWithFullMemoryInfo = 0x00000800, - MiniDumpWithThreadInfo = 0x00001000, - MiniDumpWithCodeSegs = 0x00002000, - MiniDumpWithoutAuxiliaryState = 0x00004000, - MiniDumpWithFullAuxiliaryState = 0x00008000, - MiniDumpWithPrivateWriteCopyMemory = 0x00010000, - MiniDumpIgnoreInaccessibleMemory = 0x00020000, - MiniDumpWithTokenInformation = 0x00040000, - MiniDumpWithModuleHeaders = 0x00080000, - MiniDumpFilterTriage = 0x00100000, - MiniDumpValidTypeFlags = 0x001fffff - } - - [DllImport("DbgHelp.dll", SetLastError = true)] - public static extern bool MiniDumpWriteDump(IntPtr handle, int processId, SafeFileHandle file, MiniDumpType dumpType, IntPtr exceptionParam, IntPtr userStreamParam, IntPtr callbackParam); - } - - static class Kernel32 - { - public const int MAX_PATH = 260; - public const int ERROR_NO_MORE_FILES = 0x12; - public const long INVALID_HANDLE = -1; - - public enum Toolhelp32Flags : uint - { - TH32CS_INHERIT = 0x80000000, - TH32CS_SNAPHEAPLIST = 0x00000001, - TH32CS_SNAPMODULE = 0x00000008, - TH32CS_SNAPMODULE32 = 0x00000010, - TH32CS_SNAPPROCESS = 0x00000002, - TH32CS_SNAPTHREAD = 0x00000004 - }; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public unsafe struct ProcessEntry32W - { - public int Size; - public int Usage; - public int ProcessID; - public IntPtr DefaultHeapID; - public int ModuleID; - public int Threads; - public int ParentProcessID; - public int PriClassBase; - public int Flags; - public fixed char ExeFile[MAX_PATH]; - } - - [DllImport("kernel32.dll")] - public static extern bool CloseHandle(IntPtr handle); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern IntPtr CreateToolhelp32Snapshot(Toolhelp32Flags flags, int processId); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern bool Process32FirstW(IntPtr snapshot, ref ProcessEntry32W entry); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern bool Process32NextW(IntPtr snapshot, ref ProcessEntry32W entry); - } - - static class @libproc - { - [DllImport(nameof(libproc))] - private static extern int proc_listchildpids(int ppid, int[]? buffer, int byteSize); - - public static unsafe bool ListChildPids(int ppid, out int[] buffer) - { - int n = proc_listchildpids(ppid, null, 0); - buffer = new int[n]; - return proc_listchildpids(ppid, buffer, buffer.Length * sizeof(int)) != -1; - } - } - - internal static class ProcessExtensions - { - public static bool TryGetProcessId(this Process process, out int processId) - { - try - { - processId = process.Id; - return true; - } - catch - { - // Process exited - processId = default; - return false; - } - } - - public static bool TryGetProcessName(this Process process, out string processName) - { - try - { - processName = process.ProcessName; - return true; - } - catch - { - // Process exited - processName = default; - return false; - } - } - - public unsafe static IEnumerable GetChildren(this Process process) - { - var children = new List(); - if (OperatingSystem.IsWindows()) - { - return Windows_GetChildren(process); - } - else if (OperatingSystem.IsLinux()) - { - return Linux_GetChildren(process); - } - else if (OperatingSystem.IsMacOS()) - { - return MacOS_GetChildren(process); - } - return children; - } - - private unsafe static IEnumerable Windows_GetChildren(Process process) - { - var children = new List(); - IntPtr snapshot = Kernel32.CreateToolhelp32Snapshot(Kernel32.Toolhelp32Flags.TH32CS_SNAPPROCESS, 0); - if (snapshot != IntPtr.Zero && snapshot.ToInt64() != Kernel32.INVALID_HANDLE) - { - try - { - children = new List(); - int ppid = process.Id; - - var processEntry = new Kernel32.ProcessEntry32W { Size = sizeof(Kernel32.ProcessEntry32W) }; - - bool success = Kernel32.Process32FirstW(snapshot, ref processEntry); - while (success) - { - if (processEntry.ParentProcessID == ppid) - { - try - { - children.Add(Process.GetProcessById(processEntry.ProcessID)); - } - catch {} - } - - success = Kernel32.Process32NextW(snapshot, ref processEntry); - } - - } - finally - { - Kernel32.CloseHandle(snapshot); - } - } - - return children; - } - - private static IEnumerable Linux_GetChildren(Process process) - { - var children = new List(); - List? childPids = null; - - try - { - childPids = File.ReadAllText($"/proc/{process.Id}/task/{process.Id}/children") - .Split(' ', StringSplitOptions.RemoveEmptyEntries) - .Select(pidString => int.Parse(pidString)) - .ToList(); - } - catch (IOException e) - { - // Some distros might not have the /proc/pid/task/tid/children entry enabled in the kernel - // attempt to use pgrep then - var pgrepInfo = new ProcessStartInfo("pgrep"); - pgrepInfo.RedirectStandardOutput = true; - pgrepInfo.Arguments = $"-P {process.Id}"; - - using Process pgrep = Process.Start(pgrepInfo)!; - - string[] pidStrings = pgrep.StandardOutput.ReadToEnd().Split('\n', StringSplitOptions.RemoveEmptyEntries); - pgrep.WaitForExit(); - - childPids = new List(); - foreach (var pidString in pidStrings) - if (int.TryParse(pidString, out int childPid)) - childPids.Add(childPid); - } - - foreach (var pid in childPids) - { - try - { - children.Add(Process.GetProcessById(pid)); - } - catch (ArgumentException) - { - // Ignore failure to get process, the process may have exited - } - } - - return children; - } - - private static IEnumerable MacOS_GetChildren(Process process) - { - var children = new List(); - if (libproc.ListChildPids(process.Id, out int[] childPids)) - { - foreach (var childPid in childPids) - { - children.Add(Process.GetProcessById(childPid)); - } - } - - return children; - } - } - - public class CoreclrTestWrapperLib - { - public const int EXIT_SUCCESS_CODE = 0; - public const string TIMEOUT_ENVIRONMENT_VAR = "__TestTimeout"; - - // Default timeout set to 10 minutes - public const int DEFAULT_TIMEOUT_MS = 1000 * 60 * 10; - - public const string COLLECT_DUMPS_ENVIRONMENT_VAR = "__CollectDumps"; - public const string CRASH_DUMP_FOLDER_ENVIRONMENT_VAR = "__CrashDumpFolder"; - - public const string TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR = "__TestArchitecture"; - - static bool CollectCrashDump(Process process, string crashDumpPath, StreamWriter outputWriter) - { - if (OperatingSystem.IsWindows()) - { - return CollectCrashDumpWithMiniDumpWriteDump(process, crashDumpPath, outputWriter); - } - else - { - return CollectCrashDumpWithCreateDump(process, crashDumpPath, outputWriter); - } - } - - static bool CollectCrashDumpWithMiniDumpWriteDump(Process process, string crashDumpPath, StreamWriter outputWriter) - { - bool collectedDump = false; - using (var crashDump = File.OpenWrite(crashDumpPath)) - { - var flags = DbgHelp.MiniDumpType.MiniDumpWithFullMemory | DbgHelp.MiniDumpType.MiniDumpIgnoreInaccessibleMemory; - collectedDump = DbgHelp.MiniDumpWriteDump(process.Handle, process.Id, crashDump.SafeFileHandle, flags, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - } - if (collectedDump) - { - TryPrintStackTraceFromWindowsDmp(crashDumpPath, outputWriter); - } - return collectedDump; - } - - static bool CollectCrashDumpWithCreateDump(Process process, string crashDumpPath, StreamWriter outputWriter) - { - string? coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); - if (coreRoot is null) - { - throw new InvalidOperationException("CORE_ROOT environment variable is not set."); - } - string createdumpPath = Path.Combine(coreRoot, "createdump"); - string arguments = $"--crashreport --name \"{crashDumpPath}\" {process.Id} --withheap"; - Process createdump = new Process(); - - createdump.StartInfo.FileName = "sudo"; - createdump.StartInfo.Arguments = $"{createdumpPath} {arguments}"; - - createdump.StartInfo.UseShellExecute = false; - createdump.StartInfo.RedirectStandardOutput = true; - createdump.StartInfo.RedirectStandardError = true; - - Console.WriteLine($"Invoking: {createdump.StartInfo.FileName} {createdump.StartInfo.Arguments}"); - createdump.Start(); - - Task copyOutput = createdump.StandardOutput.ReadToEndAsync(); - Task copyError = createdump.StandardError.ReadToEndAsync(); - bool fSuccess = createdump.WaitForExit(DEFAULT_TIMEOUT_MS); - - if (fSuccess) - { - Task.WaitAll(copyError, copyOutput); - string output = copyOutput.Result; - string error = copyError.Result; - - Console.WriteLine("createdump stdout:"); - Console.WriteLine(output); - Console.WriteLine("createdump stderr:"); - Console.WriteLine(error); - - TryPrintStackTraceFromCrashReport(crashDumpPath + ".crashreport.json", outputWriter); - - // Ensure the dump is accessible by current user - Process chown = new Process(); - chown.StartInfo.FileName = "sudo"; - chown.StartInfo.Arguments = $"chown \"{Environment.UserName}\" \"{crashDumpPath}\""; - - chown.StartInfo.UseShellExecute = false; - chown.StartInfo.RedirectStandardOutput = true; - chown.StartInfo.RedirectStandardError = true; - - Console.WriteLine($"Invoking: {chown.StartInfo.FileName} {chown.StartInfo.Arguments}"); - chown.Start(); - copyOutput = chown.StandardOutput.ReadToEndAsync(); - copyError = chown.StandardError.ReadToEndAsync(); - - chown.WaitForExit(DEFAULT_TIMEOUT_MS); - - Task.WaitAll(copyError, copyOutput); - Console.WriteLine("chown stdout:"); - Console.WriteLine(copyOutput.Result); - Console.WriteLine("chown stderr:"); - Console.WriteLine(copyError.Result); - } - else - { - // Workaround for https://github.com/dotnet/runtime/issues/93321 - const int MaxRetries = 5; - for (int i = 0; i < MaxRetries; i++) - { - try - { - createdump.Kill(entireProcessTree: true); - break; - } - catch (Exception e) when (i < MaxRetries - 1) - { - Console.WriteLine($"Process.Kill(entireProcessTree: true) failed:"); - Console.WriteLine(e); - Console.WriteLine("Retrying..."); - } - } - } - - return fSuccess && createdump.ExitCode == 0; - } - - private static List knownNativeModules = new List() { "libcoreclr.so", "libclrjit.so" }; - private static string TO_BE_CONTINUE_TAG = ""; - private static string SKIP_LINE_TAG = "# "; - - - static bool RunProcess(string fileName, string arguments, TextWriter outputWriter) - { - Process proc = new Process() - { - StartInfo = new ProcessStartInfo() - { - FileName = fileName, - Arguments = arguments, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - } - }; - - outputWriter.WriteLine($"Invoking: {proc.StartInfo.FileName} {proc.StartInfo.Arguments}"); - proc.Start(); - - Task stdOut = proc.StandardOutput.ReadToEndAsync(); - Task stdErr = proc.StandardError.ReadToEndAsync(); - if (!proc.WaitForExit(DEFAULT_TIMEOUT_MS)) - { - proc.Kill(true); - outputWriter.WriteLine($"Timedout: '{fileName} {arguments}"); - return false; - } - - Task.WaitAll(stdOut, stdErr); - string output = stdOut.Result; - string error = stdErr.Result; - if (!string.IsNullOrWhiteSpace(output)) - { - outputWriter.WriteLine($"stdout: {output}"); - } - if (!string.IsNullOrWhiteSpace(error)) - { - outputWriter.WriteLine($"stderr: {error}"); - } - return true; - } - - /// - /// Parse crashreport.json file, use llvm-symbolizer to extract symbols - /// and recreate the stacktrace that is printed on the console. - /// - /// crash dump path - /// Stream for writing logs - /// true, if we can print the stack trace, otherwise false. - public static bool TryPrintStackTraceFromCrashReport(string crashReportJsonFile, TextWriter outputWriter) - { - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - if (!RunProcess("sudo", $"ls -l {crashReportJsonFile}", Console.Out)) - { - return false; - } - - Console.WriteLine("========================================="); - string? userName = Environment.GetEnvironmentVariable("USER"); - if (string.IsNullOrEmpty(userName)) - { - userName = "helixbot"; - } - - if (!RunProcess("sudo", $"chmod a+rw {crashReportJsonFile}", Console.Out)) - { - return false; - } - - if (!RunProcess("sudo", $"chown {userName} {crashReportJsonFile}", Console.Out)) - { - return false; - } - - Console.WriteLine("========================================="); - if (!RunProcess("sudo", $"ls -l {crashReportJsonFile}", Console.Out)) - { - return false; - } - - Console.WriteLine("========================================="); - if (!RunProcess("ls", $"-l {crashReportJsonFile}", Console.Out)) - { - return false; - } - } - - if (!File.Exists(crashReportJsonFile)) - { - return false; - } - outputWriter.WriteLine($"Printing stacktrace from '{crashReportJsonFile}'"); - - string contents; - try - { - contents = File.ReadAllText(crashReportJsonFile); - } - catch (Exception ex) - { - Console.WriteLine($"Error reading {crashReportJsonFile}: {ex.ToString()}"); - return false; - } - var crashReport = JsonNode.Parse(contents)!; - var threads = (JsonArray)crashReport["payload"]!["threads"]!; - - // The logic happens in 3 steps: - // 1. Read the crashReport.json file, locate all the addresses of interest and then build - // a string that will be passed to llvm-symbolizer. It is populated so that each address - // is in its separate line along with the file name, etc. Some TAGS are added in the - // string that is used in step 2. - // 2. llvm-symbolizer is ran and above string is passed as input. - // 3. After llvm-symbolizer completes, TAGS are used to format its output to print it in - // the way it will be printed by sos. - - StringBuilder addrBuilder = new StringBuilder(); - string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT") ?? string.Empty; - foreach (var thread in threads) - { - - if (thread!["native_thread_id"] == null) - { - continue; - } - - addrBuilder.AppendLine(); - addrBuilder.AppendLine("----------------------------------"); - addrBuilder.AppendLine($"Thread Id: {thread["native_thread_id"]}"); - addrBuilder.AppendLine(" Child SP IP Call Site"); - var stack_frames = (JsonArray)thread["stack_frames"]!; - foreach (var frame in stack_frames) - { - addrBuilder.Append($"{SKIP_LINE_TAG} {frame!["stack_pointer"]} {frame["native_address"]} "); - bool isNative = (string)frame["is_managed"]! == "false"; - - if (isNative) - { - var nativeModuleName = (string)frame["native_module"]!; - var unmanagedName = (string)frame["unmanaged_name"]!; - - if ((nativeModuleName != null) && (knownNativeModules.Contains(nativeModuleName))) - { - // Need to use llvm-symbolizer (only if module_address != 0) - AppendAddress(addrBuilder, coreRoot, nativeModuleName, (string)frame["native_address"]!, (string)frame["module_address"]!); - } - else if ((nativeModuleName != null) || (unmanagedName != null)) - { - if (nativeModuleName != null) - { - addrBuilder.Append($"{nativeModuleName}!"); - } - if (unmanagedName != null) - { - addrBuilder.Append($"{unmanagedName}"); - } - } - } - else - { - var fileName = (string)frame["filename"]!; - var methodName = (string)frame["method_name"]!; - - if ((fileName != null) || (methodName != null)) - { - // found the managed method name - if (fileName != null) - { - addrBuilder.Append($"{fileName}!"); - } - if (methodName != null) - { - addrBuilder.Append($"{methodName}"); - } - } - else - { - addrBuilder.Append($"{frame["native_address"]}"); - } - } - addrBuilder.AppendLine(); - - } - } - - string? symbolizerOutput = null; - - Process llvmSymbolizer = new Process() - { - StartInfo = { - FileName = "llvm-symbolizer", - Arguments = $"--pretty-print", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - } - }; - - outputWriter.WriteLine($"Invoking {llvmSymbolizer.StartInfo.FileName} {llvmSymbolizer.StartInfo.Arguments}"); - - try - { - if (!llvmSymbolizer.Start()) - { - outputWriter.WriteLine($"Unable to start {llvmSymbolizer.StartInfo.FileName}"); - } - - using (var symbolizerWriter = llvmSymbolizer.StandardInput) - { - symbolizerWriter.WriteLine(addrBuilder.ToString()); - } - - Task stdout = llvmSymbolizer.StandardOutput.ReadToEndAsync(); - Task stderr = llvmSymbolizer.StandardError.ReadToEndAsync(); - bool fSuccess = llvmSymbolizer.WaitForExit(DEFAULT_TIMEOUT_MS); - - Task.WaitAll(stdout, stderr); - - if (!fSuccess) - { - outputWriter.WriteLine("Errors while running llvm-symbolizer --pretty-print"); - string output = stdout.Result; - string error = stderr.Result; - - Console.WriteLine("llvm-symbolizer stdout:"); - Console.WriteLine(output); - Console.WriteLine("llvm-symbolizer stderr:"); - Console.WriteLine(error); - - llvmSymbolizer.Kill(true); - - return false; - } - - symbolizerOutput = stdout.Result; - - } - catch (Exception e) - { - outputWriter.WriteLine("Errors while running llvm-symbolizer --pretty-print"); - outputWriter.WriteLine(e.ToString()); - return false; - } - - // Go through the output of llvm-symbolizer and strip all the markers we added initially. - string[] contentsToSantize = symbolizerOutput.Split(Environment.NewLine); - StringBuilder finalBuilder = new StringBuilder(); - for (int lineNum = 0; lineNum < contentsToSantize.Length; lineNum++) - { - string line = contentsToSantize[lineNum].Replace(SKIP_LINE_TAG, string.Empty); - if (string.IsNullOrWhiteSpace(line)) continue; - - if (line.EndsWith(TO_BE_CONTINUE_TAG)) - { - finalBuilder.Append(line.Replace(TO_BE_CONTINUE_TAG, string.Empty)); - continue; - } - finalBuilder.AppendLine(line); - } - outputWriter.WriteLine("Stack trace:"); - outputWriter.WriteLine(finalBuilder.ToString()); - return true; - } - - private static void AppendAddress(StringBuilder sb, string coreRoot, string nativeModuleName, string native_address, string module_address) - { - if (module_address != "0x0") - { - sb.Append($"{nativeModuleName}!"); - sb.Append(TO_BE_CONTINUE_TAG); - sb.AppendLine(); - //addrBuilder.AppendLine(frame.native_image_offset); - ulong nativeAddress = ulong.Parse(native_address.Substring(2), System.Globalization.NumberStyles.HexNumber); - ulong moduleAddress = ulong.Parse(module_address.Substring(2), System.Globalization.NumberStyles.HexNumber); - string fullPathToModule = Path.Combine(coreRoot, nativeModuleName); - sb.AppendFormat("{0} 0x{1:x}", fullPathToModule, nativeAddress - moduleAddress); - } - } - - public static bool TryPrintStackTraceFromWindowsDmp(string dmpFile, TextWriter outputWriter) - { - string? targetArchitecture = Environment.GetEnvironmentVariable(TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR); - if (string.IsNullOrEmpty(targetArchitecture)) - { - outputWriter.WriteLine($"Environment variable {TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR} is not set."); - return false; - } - - string cdbPath = $@"C:\Program Files (x86)\Windows Kits\10\Debuggers\{targetArchitecture}\cdb.exe"; - if (!File.Exists(cdbPath)) - { - outputWriter.WriteLine($"Unable to find cdb.exe at {cdbPath}"); - return false; - } - - string sosPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".dotnet", "sos", "sos.dll"); - - string corDllArg = string.Empty; - string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); - if (coreRoot is not null) - { - corDllArg = $".cordll -lp \"{coreRoot}\""; - } - - var cdbScriptPath = Path.GetTempFileName(); - File.WriteAllText(cdbScriptPath, $$""" - {{ corDllArg }} - .load {{sosPath}} - ~*k - !clrstack -f -all - q - """); - - // cdb outputs the stacks directly, so we don't need to parse the output. - if (!RunProcess(cdbPath, $@"-c ""$<{cdbScriptPath}"" -z ""{dmpFile}""", outputWriter)) - { - outputWriter.WriteLine("Unable to run cdb.exe"); - return false; - } - return true; - } - - // Finds all children processes starting with a process named childName - // The children are sorted in the order they should be dumped - static unsafe IEnumerable FindChildProcessesByName(Process process, string childName) - { - process.TryGetProcessName(out string parentProcessName); - process.TryGetProcessId(out int parentProcessId); - Console.WriteLine($"Finding all child processes of '{parentProcessName}' (ID: {parentProcessId}) with name '{childName}'"); - - var children = new Stack(); - Queue childrenToCheck = new Queue(); - HashSet seen = new HashSet(); - - seen.Add(parentProcessId); - - try - { - foreach (var child in process.GetChildren()) - childrenToCheck.Enqueue(child); - } - catch - { - // Process exited - } - - while (childrenToCheck.Count != 0) - { - Process child = childrenToCheck.Dequeue(); - - if (!child.TryGetProcessId(out int processId)) - continue; - - if (seen.Contains(processId)) - continue; - - if (!child.TryGetProcessName(out string processName)) - continue; - - Console.WriteLine($"Checking child process: '{processName}' (ID: {processId})"); - seen.Add(processId); - - try - { - foreach (var grandchild in child.GetChildren()) - childrenToCheck.Enqueue(grandchild); - } - catch - { - // Process exited - } - - if (processName.Equals(childName, StringComparison.OrdinalIgnoreCase)) - { - children.Push(child); - } - } - - return children; - } - - public int RunTest(string executable, string outputFile, string errorFile, string category, string testBinaryBase, string outputDir) - { - Debug.Assert(outputFile != errorFile); - - int exitCode = -100; - - // If a timeout was given to us by an environment variable, use it instead of the default - // timeout. - string? environmentVar = Environment.GetEnvironmentVariable(TIMEOUT_ENVIRONMENT_VAR); - int timeout = environmentVar != null ? int.Parse(environmentVar) : DEFAULT_TIMEOUT_MS; - bool collectCrashDumps = Environment.GetEnvironmentVariable(COLLECT_DUMPS_ENVIRONMENT_VAR) != null; - string? crashDumpFolder = Environment.GetEnvironmentVariable(CRASH_DUMP_FOLDER_ENVIRONMENT_VAR); - - var outputStream = new FileStream(outputFile, FileMode.Create); - var errorStream = new FileStream(errorFile, FileMode.Create); - - using (var outputWriter = new StreamWriter(outputStream)) - using (var errorWriter = new StreamWriter(errorStream)) - using (Process process = new Process()) - { - if (MobileAppHandler.IsRetryRequested(testBinaryBase)) - { - outputWriter.WriteLine("\nWork item retry had been requested earlier - skipping test..."); - } - else - { - // Windows can run the executable implicitly - if (OperatingSystem.IsWindows()) - { - process.StartInfo.FileName = executable; - } - // Non-windows needs to be told explicitly to run through /bin/bash shell - else - { - process.StartInfo.FileName = "/bin/bash"; - process.StartInfo.Arguments = executable; - } - - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.EnvironmentVariables.Add("__Category", category); - process.StartInfo.EnvironmentVariables.Add("__TestBinaryBase", testBinaryBase); - process.StartInfo.EnvironmentVariables.Add("__OutputDir", outputDir); - - DateTime startTime = DateTime.Now; - process.Start(); - - var cts = new CancellationTokenSource(); - Task copyOutput = process.StandardOutput.BaseStream.CopyToAsync(outputStream, 4096, cts.Token); - Task copyError = process.StandardError.BaseStream.CopyToAsync(errorStream, 4096, cts.Token); - - if (process.WaitForExit(timeout)) - { - // Process completed. Check process.ExitCode here. - exitCode = process.ExitCode; - MobileAppHandler.CheckExitCode(exitCode, testBinaryBase, category, outputWriter); - Task.WaitAll(copyOutput, copyError); - - if (exitCode != 0) - { - // Search for dump, if created. - if (Directory.Exists(crashDumpFolder)) - { - outputWriter.WriteLine($"Test failed. Trying to see if dump file was created in {crashDumpFolder} since {startTime}"); - DirectoryInfo crashDumpFolderInfo = new DirectoryInfo(crashDumpFolder); - // crashreport is only for non-windows. - if (!OperatingSystem.IsWindows()) - { - var dmpFilesInfo = crashDumpFolderInfo.GetFiles("*.crashreport.json").OrderByDescending(f => f.CreationTime); - foreach (var dmpFile in dmpFilesInfo) - { - if (dmpFile.CreationTime < startTime) - { - // No new files since test started. - outputWriter.WriteLine("Finish looking for *.crashreport.json. No new files created."); - break; - } - outputWriter.WriteLine($"Processing {dmpFile.FullName}"); - TryPrintStackTraceFromCrashReport(dmpFile.FullName, outputWriter); - } - } - else - { - var dmpFilesInfo = crashDumpFolderInfo.GetFiles("*.dmp").OrderByDescending(f => f.CreationTime); - foreach (var dmpFile in dmpFilesInfo) - { - if (dmpFile.CreationTime < startTime) - { - // No new files since test started. - outputWriter.WriteLine("Finished looking for *.dmp. No new files created."); - break; - } - outputWriter.WriteLine($"Processing {dmpFile.FullName}"); - TryPrintStackTraceFromWindowsDmp(dmpFile.FullName, outputWriter); - } - } - } - } - } - else - { - // Timed out. - DateTime endTime = DateTime.Now; - - try - { - cts.Cancel(); - } - catch { } - - outputWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}{2}{3}, start: {4}, end: {5})", - executable, timeout, (environmentVar != null) ? " from variable " : "", (environmentVar != null) ? TIMEOUT_ENVIRONMENT_VAR : "", - startTime.ToString(), endTime.ToString()); - outputWriter.Flush(); - errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}{2}{3}, start: {4}, end: {5})", - executable, timeout, (environmentVar != null) ? " from variable " : "", (environmentVar != null) ? TIMEOUT_ENVIRONMENT_VAR : "", - startTime.ToString(), endTime.ToString()); - errorWriter.Flush(); - - Console.WriteLine("Collecting diagnostic information..."); - Console.WriteLine("Snapshot of processes currently running:"); - Console.WriteLine($"\t{"ID",-6} ProcessName"); - foreach (var activeProcess in Process.GetProcesses()) - { - activeProcess.TryGetProcessName(out string activeProcessName); - activeProcess.TryGetProcessId(out int activeProcessId); - Console.WriteLine($"\t{activeProcessId,-6} {activeProcessName}"); - } - - if (OperatingSystem.IsWindows()) - { - Console.WriteLine("Snapshot of processes currently running (using wmic):"); - Console.WriteLine(GetAllProcessNames_wmic()); - } - - if (collectCrashDumps) - { - if (crashDumpFolder != null) - { - foreach (var child in FindChildProcessesByName(process, "corerun")) - { - string crashDumpPath = Path.Combine(Path.GetFullPath(crashDumpFolder), string.Format("crashdump_{0}.dmp", child.Id)); - Console.WriteLine($"Attempting to collect crash dump: {crashDumpPath}"); - if (CollectCrashDump(child, crashDumpPath, outputWriter)) - { - Console.WriteLine("Collected crash dump: {0}", crashDumpPath); - } - else - { - Console.WriteLine("Failed to collect crash dump"); - } - } - } - } - - // kill the timed out processes after we've collected dumps - process.Kill(entireProcessTree: true); - } - } - - outputWriter.WriteLine("Test Harness Exitcode is : " + exitCode.ToString()); - outputWriter.Flush(); - errorWriter.Flush(); - } - - return exitCode; - } - - private static string GetAllProcessNames_wmic() - { - // The command to execute - string command = "wmic process get Name, ProcessId, ParentProcessId"; - - // Start the process and capture the output - Process process = new Process(); - process.StartInfo.FileName = "cmd.exe"; - process.StartInfo.Arguments = $"/c {command}"; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - - // Start the process and read the output - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(100); // wait for 100 ms - - // Output the result - return output; - } - } -} diff --git a/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs b/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs deleted file mode 100644 index 6612b27227ad96..00000000000000 --- a/src/tests/Common/Coreclr.TestWrapper/MobileAppHandler.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace CoreclrTestLib -{ - public class MobileAppHandler - { - // See https://github.com/dotnet/xharness/blob/main/src/Microsoft.DotNet.XHarness.Common/CLI/ExitCode.cs - // 78 - PACKAGE_INSTALLATION_FAILURE - // 81 - DEVICE_NOT_FOUND - // 82 - RETURN_CODE_NOT_SET - // 83 - APP_LAUNCH_FAILURE - // 84 - DEVICE_FILE_COPY_FAILURE - // 86 - PACKAGE_INSTALLATION_TIMEOUT - // 88 - SIMULATOR_FAILURE - // 89 - DEVICE_FAILURE - // 90 - APP_LAUNCH_TIMEOUT - // 91 - ADB_FAILURE - private static readonly int[] _knownExitCodes = new int[] { 78, 81, 82, 83, 84, 86, 88, 89, 90, 91 }; - - public int InstallMobileApp(string platform, string category, string testBinaryBase, string reportBase, string targetOS) - { - return HandleMobileApp("install", platform, category, testBinaryBase, reportBase, targetOS); - } - - public int UninstallMobileApp(string platform, string category, string testBinaryBase, string reportBase, string targetOS) - { - return HandleMobileApp("uninstall", platform, category, testBinaryBase, reportBase, targetOS); - } - - private static int HandleMobileApp(string action, string platform, string category, string testBinaryBase, string reportBase, string targetOS) - { - int exitCode = -100; - - string outputFile = Path.Combine(reportBase, action, $"{category}_{action}.output.txt"); - string errorFile = Path.Combine(reportBase, action, $"{category}_{action}.error.txt"); - bool platformValueFlag = true; - bool actionValueFlag = true; - - Directory.CreateDirectory(Path.Combine(reportBase, action)); - var outputStream = new FileStream(outputFile, FileMode.Create); - var errorStream = new FileStream(errorFile, FileMode.Create); - - using (var outputWriter = new StreamWriter(outputStream)) - using (var errorWriter = new StreamWriter(errorStream)) - { - if ((platform != "android") && (platform != "apple")) - { - outputWriter.WriteLine($"Incorrect value of platform. Provided {platform}. Valid strings are android and apple."); - platformValueFlag = false; - } - - if ((action != "install") && (action != "uninstall")) - { - outputWriter.WriteLine($"Incorrect value of action. Provided {action}. Valid strings are install and uninstall."); - actionValueFlag = false; - } - - if (platformValueFlag && actionValueFlag) - { - int timeout = 240000; // Set timeout to 4 mins, because the installation on Android arm64/32 devices could take up to 10 mins on CI - string? dotnetCmd_raw = System.Environment.GetEnvironmentVariable("__TestDotNetCmd"); - string? xharnessCmd_raw = System.Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); - string dotnetCmd = string.IsNullOrEmpty(dotnetCmd_raw) ? "dotnet" : dotnetCmd_raw; - string xharnessCmd = string.IsNullOrEmpty(xharnessCmd_raw) ? "xharness" : $"exec {xharnessCmd_raw}"; - string appExtension = platform == "android" ? "apk" : "app"; - - string cmdStr = $"{dotnetCmd} {xharnessCmd} {platform} {action}"; - - if (platform == "android") - { - cmdStr += $" --package-name=net.dot.{category}"; - - if (action == "install") - { - cmdStr += $" --app={testBinaryBase}/{category}.{appExtension} --output-directory={reportBase}/{action}"; - } - } - else // platform is apple - { - string targetString = ""; - - switch (targetOS) { - case "ios": - targetString = "ios-device"; - break; - case "iossimulator": - targetString = "ios-simulator-64"; - break; - case "tvos": - targetString = "tvos-device"; - break; - case "tvossimulator": - targetString = "tvos-simulator"; - break; - } - - cmdStr += $" --output-directory={reportBase}/{action} --target={targetString}"; - - if (action == "install") - { - cmdStr += $" --app={testBinaryBase}/{category}.{appExtension}"; - } - else // action is uninstall - { - cmdStr += $" --app=net.dot.{category}"; - } - } - - if (action == "install") - { - cmdStr += " --timeout 00:02:30"; - } - - using (Process process = new Process()) - { - if (OperatingSystem.IsWindows()) - { - process.StartInfo.FileName = "cmd.exe"; - } - else - { - process.StartInfo.FileName = "/bin/bash"; - } - - process.StartInfo.Arguments = ConvertCmd2Arg(cmdStr); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - - DateTime startTime = DateTime.Now; - process.Start(); - - var cts = new CancellationTokenSource(); - Task copyOutput = process.StandardOutput.BaseStream.CopyToAsync(outputStream, 4096, cts.Token); - Task copyError = process.StandardError.BaseStream.CopyToAsync(errorStream, 4096, cts.Token); - - if (process.WaitForExit(timeout)) - { - // Process completed. - exitCode = process.ExitCode; - CheckExitCode(exitCode, testBinaryBase, category, outputWriter); - Task.WaitAll(copyOutput, copyError); - } - else - { - //Time out. - DateTime endTime = DateTime.Now; - - try - { - cts.Cancel(); - } - catch {} - - outputWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", - cmdStr, timeout, startTime.ToString(), endTime.ToString()); - errorWriter.WriteLine("\ncmdLine:{0} Timed Out (timeout in milliseconds: {1}, start: {2}, end: {3})", - cmdStr, timeout, startTime.ToString(), endTime.ToString()); - - process.Kill(entireProcessTree: true); - } - } - } - - outputWriter.WriteLine("xharness exitcode is : " + exitCode.ToString()); - outputWriter.Flush(); - errorWriter.Flush(); - } - - return exitCode; - } - - private static string ConvertCmd2Arg(string cmd) - { - cmd.Replace("\"", "\"\""); - - string cmdPrefix; - if(OperatingSystem.IsWindows()) - { - cmdPrefix = "/c"; - } - else - { - cmdPrefix = "-c"; - } - - return $"{cmdPrefix} \"{cmd}\""; - } - - private static void CreateRetryFile(string fileName, int exitCode, string appName) - { - using (StreamWriter writer = new StreamWriter(fileName)) - { - writer.WriteLine($"appName: {appName}; exitCode: {exitCode}"); - } - } - - public static void CheckExitCode(int exitCode, string testBinaryBase, string category, StreamWriter outputWriter) - { - if (_knownExitCodes.Contains(exitCode)) - { - CreateRetryFile($"{testBinaryBase}/.retry", exitCode, category); - outputWriter.WriteLine("\nInfra issue was detected and a work item retry was requested"); - } - } - - public static bool IsRetryRequested(string testBinaryBase) - { - return File.Exists($"{testBinaryBase}/.retry"); - } - } -} diff --git a/src/tests/Common/XUnitLogChecker/XUnitLogChecker.cs b/src/tests/Common/XUnitLogChecker/XUnitLogChecker.cs index 2c26fda5b52683..32732581a53a7d 100644 --- a/src/tests/Common/XUnitLogChecker/XUnitLogChecker.cs +++ b/src/tests/Common/XUnitLogChecker/XUnitLogChecker.cs @@ -3,13 +3,14 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; -using CoreclrTestWrapperLib = CoreclrTestLib.CoreclrTestWrapperLib; - public class XUnitLogChecker { private static class Patterns @@ -512,8 +513,7 @@ static void PrintStackTracesFromDumps(string testLogPath) WriteLineTimestamp($"Reading crash dump '{dumpPath}'..."); WriteLineTimestamp("Stack Trace Found:\n"); - CoreclrTestWrapperLib.TryPrintStackTraceFromWindowsDmp(dumpPath, - Console.Out); + TryPrintStackTraceFromWindowsDmp(dumpPath, Console.Out); } else { @@ -529,10 +529,336 @@ static void PrintStackTracesFromDumps(string testLogPath) WriteLineTimestamp($"Reading crash report '{crashReportPath}'..."); WriteLineTimestamp("Stack Trace Found:\n"); - CoreclrTestWrapperLib.TryPrintStackTraceFromCrashReport(crashReportPath, - Console.Out); + TryPrintStackTraceFromCrashReport(crashReportPath, Console.Out); + } + } + } + + private const int DEFAULT_TIMEOUT_MS = 1000 * 60 * 10; + private const string TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR = "__TestArchitecture"; + private static readonly List s_knownNativeModules = new List() { "libcoreclr.so", "libclrjit.so" }; + private const string TO_BE_CONTINUE_TAG = ""; + private const string SKIP_LINE_TAG = "# "; + + static bool RunProcess(string fileName, string arguments, TextWriter outputWriter) + { + Process proc = new Process() + { + StartInfo = new ProcessStartInfo() + { + FileName = fileName, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + } + }; + + outputWriter.WriteLine($"Invoking: {proc.StartInfo.FileName} {proc.StartInfo.Arguments}"); + proc.Start(); + + Task stdOut = proc.StandardOutput.ReadToEndAsync(); + Task stdErr = proc.StandardError.ReadToEndAsync(); + if (!proc.WaitForExit(DEFAULT_TIMEOUT_MS)) + { + proc.Kill(true); + outputWriter.WriteLine($"Timedout: '{fileName} {arguments}"); + return false; + } + + Task.WaitAll(stdOut, stdErr); + string output = stdOut.Result; + string error = stdErr.Result; + if (!string.IsNullOrWhiteSpace(output)) + { + outputWriter.WriteLine($"stdout: {output}"); + } + if (!string.IsNullOrWhiteSpace(error)) + { + outputWriter.WriteLine($"stderr: {error}"); + } + return true; + } + + /// + /// Parse crashreport.json file, use llvm-symbolizer to extract symbols + /// and recreate the stacktrace that is printed on the console. + /// + /// crash dump path + /// Stream for writing logs + /// true, if we can print the stack trace, otherwise false. + static bool TryPrintStackTraceFromCrashReport(string crashReportJsonFile, TextWriter outputWriter) + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + if (!RunProcess("sudo", $"ls -l {crashReportJsonFile}", Console.Out)) + { + return false; + } + + Console.WriteLine("========================================="); + string? userName = Environment.GetEnvironmentVariable("USER"); + if (string.IsNullOrEmpty(userName)) + { + userName = "helixbot"; + } + + if (!RunProcess("sudo", $"chmod a+rw {crashReportJsonFile}", Console.Out)) + { + return false; + } + + if (!RunProcess("sudo", $"chown {userName} {crashReportJsonFile}", Console.Out)) + { + return false; + } + + Console.WriteLine("========================================="); + if (!RunProcess("sudo", $"ls -l {crashReportJsonFile}", Console.Out)) + { + return false; + } + + Console.WriteLine("========================================="); + if (!RunProcess("ls", $"-l {crashReportJsonFile}", Console.Out)) + { + return false; + } + } + + if (!File.Exists(crashReportJsonFile)) + { + return false; + } + outputWriter.WriteLine($"Printing stacktrace from '{crashReportJsonFile}'"); + + string contents; + try + { + contents = File.ReadAllText(crashReportJsonFile); + } + catch (Exception ex) + { + Console.WriteLine($"Error reading {crashReportJsonFile}: {ex.ToString()}"); + return false; + } + var crashReport = JsonNode.Parse(contents)!; + var threads = (JsonArray)crashReport["payload"]!["threads"]!; + + // The logic happens in 3 steps: + // 1. Read the crashReport.json file, locate all the addresses of interest and then build + // a string that will be passed to llvm-symbolizer. It is populated so that each address + // is in its separate line along with the file name, etc. Some TAGS are added in the + // string that is used in step 2. + // 2. llvm-symbolizer is ran and above string is passed as input. + // 3. After llvm-symbolizer completes, TAGS are used to format its output to print it in + // the way it will be printed by sos. + + StringBuilder addrBuilder = new StringBuilder(); + string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT") ?? string.Empty; + foreach (var thread in threads) + { + + if (thread!["native_thread_id"] is null) + { + continue; + } + + addrBuilder.AppendLine(); + addrBuilder.AppendLine("----------------------------------"); + addrBuilder.AppendLine($"Thread Id: {thread["native_thread_id"]}"); + addrBuilder.AppendLine(" Child SP IP Call Site"); + var stack_frames = (JsonArray)thread["stack_frames"]!; + foreach (var frame in stack_frames) + { + addrBuilder.Append($"{SKIP_LINE_TAG} {frame!["stack_pointer"]} {frame["native_address"]} "); + bool isNative = (string)frame["is_managed"]! == "false"; + + if (isNative) + { + var nativeModuleName = (string?)frame["native_module"]; + var unmanagedName = (string?)frame["unmanaged_name"]; + + if ((nativeModuleName is not null) && (s_knownNativeModules.Contains(nativeModuleName))) + { + // Need to use llvm-symbolizer (only if module_address != 0) + AppendAddress(addrBuilder, coreRoot, nativeModuleName, (string)frame["native_address"]!, (string)frame["module_address"]!); + } + else if ((nativeModuleName is not null) || (unmanagedName is not null)) + { + if (nativeModuleName is not null) + { + addrBuilder.Append($"{nativeModuleName}!"); + } + if (unmanagedName is not null) + { + addrBuilder.Append($"{unmanagedName}"); + } + } + } + else + { + var fileName = (string?)frame["filename"]; + var methodName = (string?)frame["method_name"]; + + if ((fileName is not null) || (methodName is not null)) + { + // found the managed method name + if (fileName is not null) + { + addrBuilder.Append($"{fileName}!"); + } + if (methodName is not null) + { + addrBuilder.Append($"{methodName}"); + } + } + else + { + addrBuilder.Append($"{frame["native_address"]}"); + } + } + addrBuilder.AppendLine(); + } } + + string? symbolizerOutput = null; + + Process llvmSymbolizer = new Process() + { + StartInfo = { + FileName = "llvm-symbolizer", + Arguments = $"--pretty-print", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + } + }; + + outputWriter.WriteLine($"Invoking {llvmSymbolizer.StartInfo.FileName} {llvmSymbolizer.StartInfo.Arguments}"); + + try + { + if (!llvmSymbolizer.Start()) + { + outputWriter.WriteLine($"Unable to start {llvmSymbolizer.StartInfo.FileName}"); + } + + using (var symbolizerWriter = llvmSymbolizer.StandardInput) + { + symbolizerWriter.WriteLine(addrBuilder.ToString()); + } + + Task stdout = llvmSymbolizer.StandardOutput.ReadToEndAsync(); + Task stderr = llvmSymbolizer.StandardError.ReadToEndAsync(); + bool fSuccess = llvmSymbolizer.WaitForExit(DEFAULT_TIMEOUT_MS); + + Task.WaitAll(stdout, stderr); + + if (!fSuccess) + { + outputWriter.WriteLine("Errors while running llvm-symbolizer --pretty-print"); + string output = stdout.Result; + string error = stderr.Result; + + Console.WriteLine("llvm-symbolizer stdout:"); + Console.WriteLine(output); + Console.WriteLine("llvm-symbolizer stderr:"); + Console.WriteLine(error); + + llvmSymbolizer.Kill(true); + + return false; + } + + symbolizerOutput = stdout.Result; + + } + catch (Exception e) + { + outputWriter.WriteLine("Errors while running llvm-symbolizer --pretty-print"); + outputWriter.WriteLine(e.ToString()); + return false; + } + + // Go through the output of llvm-symbolizer and strip all the markers we added initially. + string[] contentsToSantize = symbolizerOutput.Split(Environment.NewLine); + StringBuilder finalBuilder = new StringBuilder(); + for (int lineNum = 0; lineNum < contentsToSantize.Length; lineNum++) + { + string line = contentsToSantize[lineNum].Replace(SKIP_LINE_TAG, string.Empty); + if (string.IsNullOrWhiteSpace(line)) continue; + + if (line.EndsWith(TO_BE_CONTINUE_TAG)) + { + finalBuilder.Append(line.Replace(TO_BE_CONTINUE_TAG, string.Empty)); + continue; + } + finalBuilder.AppendLine(line); + } + outputWriter.WriteLine("Stack trace:"); + outputWriter.WriteLine(finalBuilder.ToString()); + return true; + } + + static void AppendAddress(StringBuilder sb, string coreRoot, string nativeModuleName, string native_address, string module_address) + { + if (module_address != "0x0") + { + sb.Append($"{nativeModuleName}!"); + sb.Append(TO_BE_CONTINUE_TAG); + sb.AppendLine(); + //addrBuilder.AppendLine(frame.native_image_offset); + ulong nativeAddress = ulong.Parse(native_address.Substring(2), System.Globalization.NumberStyles.HexNumber); + ulong moduleAddress = ulong.Parse(module_address.Substring(2), System.Globalization.NumberStyles.HexNumber); + string fullPathToModule = Path.Combine(coreRoot, nativeModuleName); + sb.AppendFormat("{0} 0x{1:x}", fullPathToModule, nativeAddress - moduleAddress); + } + } + + static bool TryPrintStackTraceFromWindowsDmp(string dmpFile, TextWriter outputWriter) + { + string? targetArchitecture = Environment.GetEnvironmentVariable(TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR); + if (string.IsNullOrEmpty(targetArchitecture)) + { + outputWriter.WriteLine($"Environment variable {TEST_TARGET_ARCHITECTURE_ENVIRONMENT_VAR} is not set."); + return false; + } + + string cdbPath = $@"C:\Program Files (x86)\Windows Kits\10\Debuggers\{targetArchitecture}\cdb.exe"; + if (!File.Exists(cdbPath)) + { + outputWriter.WriteLine($"Unable to find cdb.exe at {cdbPath}"); + return false; + } + + string sosPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".dotnet", "sos", "sos.dll"); + + string corDllArg = string.Empty; + string? coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); + if (coreRoot is not null) + { + corDllArg = $".cordll -lp \"{coreRoot}\""; + } + + var cdbScriptPath = Path.GetTempFileName(); + File.WriteAllText(cdbScriptPath, $$""" + {{ corDllArg }} + .load {{sosPath}} + ~*k + !clrstack -f -all + q + """); + + // cdb outputs the stacks directly, so we don't need to parse the output. + if (!RunProcess(cdbPath, $@"-c ""$<{cdbScriptPath}"" -z ""{dmpFile}""", outputWriter)) + { + outputWriter.WriteLine("Unable to run cdb.exe"); + return false; + } + return true; } } diff --git a/src/tests/Common/XUnitLogChecker/XUnitLogChecker.csproj b/src/tests/Common/XUnitLogChecker/XUnitLogChecker.csproj index 4d5a2881ac1823..5b06ca87506641 100644 --- a/src/tests/Common/XUnitLogChecker/XUnitLogChecker.csproj +++ b/src/tests/Common/XUnitLogChecker/XUnitLogChecker.csproj @@ -15,9 +15,4 @@ - - - - - diff --git a/src/tests/Common/XUnitWrapperGenerator/OptionsHelper.cs b/src/tests/Common/XUnitWrapperGenerator/OptionsHelper.cs index f6b0884719eba1..c1cb445218062f 100644 --- a/src/tests/Common/XUnitWrapperGenerator/OptionsHelper.cs +++ b/src/tests/Common/XUnitWrapperGenerator/OptionsHelper.cs @@ -8,6 +8,7 @@ public static class OptionsHelper { private const string InMergedTestDirectoryOption = "build_property.InMergedTestDirectory"; private const string IsMergedTestRunnerAssemblyOption = "build_property.IsMergedTestRunnerAssembly"; + private const string BuildAsStandaloneOption = "build_property.BuildAsStandalone"; private const string CLRTestPriorityToBuildOption = "build_property.CLRTestPriorityToBuild"; private const string TestBuildModeOption = "build_property.TestBuildMode"; private const string RuntimeFlavorOption = "build_property.RuntimeFlavor"; @@ -35,6 +36,8 @@ private static bool GetBoolOption(this AnalyzerConfigOptions options, string key internal static bool IsMergedTestRunnerAssembly(this AnalyzerConfigOptions options) => options.GetBoolOption(IsMergedTestRunnerAssemblyOption); + internal static bool BuildAsStandalone(this AnalyzerConfigOptions options) => options.GetBoolOption(BuildAsStandaloneOption); + internal static int? CLRTestPriorityToBuild(this AnalyzerConfigOptions options) => options.GetIntOption(CLRTestPriorityToBuildOption); internal static string RuntimeFlavor(this AnalyzerConfigOptions options) => options.TryGetValue(RuntimeFlavorOption, out string? flavor) ? flavor : "CoreCLR"; diff --git a/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs b/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs index 424a04fb39ec63..1f0b3ab0765c91 100644 --- a/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs +++ b/src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs @@ -251,11 +251,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static void AddRunnerSource(SourceProductionContext context, ImmutableArray methods, AnalyzerConfigOptionsProvider configOptions, ImmutableDictionary aliasMap, CompData compData) { - bool isMergedTestRunnerAssembly = configOptions.GlobalOptions.IsMergedTestRunnerAssembly(); + bool buildAsMergedRunner = configOptions.GlobalOptions.IsMergedTestRunnerAssembly() && !configOptions.GlobalOptions.BuildAsStandalone(); configOptions.GlobalOptions.TryGetValue("build_property.TargetOS", out string? targetOS); string assemblyName = compData.AssemblyName; - if (isMergedTestRunnerAssembly) + if (buildAsMergedRunner) { if (targetOS?.ToLowerInvariant() is "ios" or "iossimulator" or "tvos" or "tvossimulator" or "maccatalyst" or "android" or "browser") { diff --git a/src/tests/Common/helixpublishwitharcade.proj b/src/tests/Common/helixpublishwitharcade.proj index 1e18c685532b2e..29d09f063bb463 100644 --- a/src/tests/Common/helixpublishwitharcade.proj +++ b/src/tests/Common/helixpublishwitharcade.proj @@ -70,8 +70,6 @@ $([MSBuild]::NormalizeDirectory($(CoreRootDirectory))) $(TestBinDir)XUnitLogChecker\ $([MSBuild]::NormalizeDirectory($(XUnitLogCheckerDirectory))) - $(TestBinDir)LegacyPayloads\ - $([MSBuild]::NormalizeDirectory($(LegacyPayloadsRootDirectory))) $(TestBinDir)MergedPayloads\ $([MSBuild]::NormalizeDirectory($(MergedPayloadsRootDirectory))) AppBundle @@ -128,7 +126,6 @@ - @@ -208,68 +205,6 @@ - - - <_XUnitWrapperDll Include="@(XUnitWrapperGrouping)" /> - - - <_XUnitWrapperDll Include="$(TestBinDir)**\*.XUnitWrapper.dll" Exclude="$(LegacyPayloadsRootDirectory)**\*.XUnitWrapper.dll;@(XUnitWrapperGrouping->Metadata('FullPath'))"> - - - - - - <_XUnitWrapperDll Update="@(_XUnitWrapperDll)"> - $([MSBuild]::ValueOrDefault(%(FileName),'').Replace('.XUnitWrapper','')) - - - - - - - <_FileDirectory>%(_XUnitWrapperDll.RootDir)%(Directory) - <_PayloadGroup>%(_XUnitWrapperDll.PayloadGroup) - <_XUnitWrapperDll>%(_XUnitWrapperDll.FullPath) - - - - <_LegacyPayloadFiles Include="$(_FileDirectory)**" /> - <_LegacyPayloadFiles Update="@(_LegacyPayloadFiles)"> - - $(_PayloadGroup)\$([System.IO.Path]::GetRelativePath($(TestBinDir), %(FullPath))) - - - - - - - - - - - - - - - - @(_MergedWrapperRunScript->'%(RootDir)%(Directory)publish/*') - $(PublishedMergedPayloadsToExclude);@(_MergedWrapperRunScript->'%(RootDir)%(Directory)$(MobileAppBundleDirName)/**') - - - - - - - - - @@ -298,10 +233,7 @@ <_PayloadGroups Include="$(PayloadGroups)" /> - <_ProjectsToBuild Include="testenvironment.proj" Condition="!$(IsMergedTestWrapper)"> - Scenario=$(Scenario);TestEnvFileName=$(LegacyPayloadsRootDirectory)%(_PayloadGroups.Identity)\$(TestEnvFileName);TargetsWindows=$(TestWrapperTargetsWindows);RuntimeVariant=$(_RuntimeVariant) - - <_ProjectsToBuild Include="testenvironment.proj" Condition="$(IsMergedTestWrapper)"> + <_ProjectsToBuild Include="testenvironment.proj"> Scenario=$(Scenario);TestEnvFileName=$(MergedPayloadsRootDirectory)%(_PayloadGroups.Identity)\$(TestEnvFileName);TargetsWindows=$(TestWrapperTargetsWindows);RuntimeVariant=$(_RuntimeVariant) @@ -309,25 +241,17 @@ - - - - - <_LegacyPayloadGroups Include="@(_XUnitWrapperDll->Metadata('PayloadGroup')->DistinctWithCase())" /> - <_Scenario Include="$(_Scenarios.Split(','))" /> - <_ProjectsToBuild Include="$(MSBuildProjectFile)"> - Scenario=%(_Scenario.Identity);PayloadGroups=@(_LegacyPayloadGroups);IsMergedTestWrapper=false - - - - - - - + <_Scenario Include="$(_Scenarios.Split(','))" /> <_ProjectsToBuild Include="$(MSBuildProjectFile)"> - Scenario=%(_Scenario.Identity);PayloadGroups=@(_MergedPayloadGroups);IsMergedTestWrapper=true + Scenario=%(_Scenario.Identity);PayloadGroups=@(_MergedPayloadGroups) @@ -623,29 +547,6 @@ Condition="'@(_TestExclusionListPlaceholder)' != ''" /> - - - - - - - $([MSBuild]::MakeRelative($(LegacyPayloadsRootDirectory), %(FullPath))) - %(FullPath) - - - - - - @@ -827,20 +728,6 @@ sos https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2/dotnet-sos/$(DotnetSosVersion)/dotnet-sos.$(DotnetSosVersion).nupkg - - - - $([MSBuild]::MakeRelative($(LegacyPayloadsRootDirectory), %(FullPath))) - %(FullPath) - $([System.String]::Join(' ', $([System.IO.Directory]::GetFiles(%(FullPath), '*.XUnitWrapper.dll', SearchOption.AllDirectories))).Replace($([MSBuild]::EnsureTrailingSlash(%(FullPath))),'')) - - - - $(LegacyPayloadsRootDirectory)\%(PayloadGroup).zip - @@ -1004,18 +891,6 @@ - - %(PayloadDirectory) - $(_WorkaroundForNuGetMigrationsForPrepending) dotnet $(XUnitRunnerDll) %(XUnitWrapperDlls) $(XUnitRunnerArgs) - $([System.TimeSpan]::FromMinutes($(TimeoutPerTestCollectionInMinutes))) - coreclr_tests.run.$(TargetOS).$(TargetArchitecture).$(Configuration).mch;coreclr_tests.run.$(TargetOS).$(TargetArchitecture).$(Configuration).log - - - - $([System.TimeSpan]::FromMinutes($(TimeoutPerTestCollectionInMinutes))) - dotnet $(XUnitRunnerDll) %(XUnitWrapperDlls) $(XUnitRunnerArgs) - - --arg=env:TestExclusionListPath=TestExclusionList.txt net.dot.%(ApkFileName) @@ -1023,12 +898,6 @@ $([System.TimeSpan]::FromMinutes($(TimeoutPerTestCollectionInMinutes))) - - $(AppleTestTarget) - $([System.TimeSpan]::FromMinutes($(TimeoutPerTestCollectionInMinutes))) - $(SigningCommand) dotnet $(XUnitRunnerDll) %(XUnitWrapperDlls) $(XUnitRunnerArgs) - - --set-env=TestExclusionListPath=TestExclusionList.txt $(AppleTestTarget) diff --git a/src/tests/Common/tests.targets b/src/tests/Common/tests.targets index 2aa062c2f8b6f9..1d81103b1ec578 100644 --- a/src/tests/Common/tests.targets +++ b/src/tests/Common/tests.targets @@ -11,15 +11,15 @@ <__TestRunXmlLog Condition="'$(__TestRunXmlLog)' == ''">$(__LogsDir)\TestRun.xml - + + - - + <_StandaloneRunnerMarker Include="$(BaseOutputPathWithConfig)\**\*.StandaloneTestRunner" /> + + + $([System.IO.Path]::ChangeExtension('%(MergedTestWrapperScripts.Identity)', '.log')) + - - - - @@ -38,44 +38,11 @@ - - - - - category=outerloop;category=failing - - collections - - - - - - - - - $(CORE_ROOT)\xunit\xunit.console.dll - - -parallel none - -parallel $(ParallelRun) - $(XunitArgs) -html $(__TestRunHtmlLog) - $(XunitArgs) -xml $(__TestRunXmlLog) - $(XunitArgs) @(IncludeTraitsItems->'-trait %(Identity)', ' ') - $(XunitArgs) @(ExcludeTraitsItems->'-notrait %(Identity)', ' ') - - - $(XunitArgs) -nocolor - - - - - <_TestAssembliesRelative Include="@(TestAssemblies -> Replace('$(BaseOutputPathWithConfig)', '.\'))" /> - - - + DependsOnTargets="FindBuildAllTestsAsStandaloneRunnerScripts;FindMergedTestDirectories"> + + @@ -97,20 +64,43 @@ - - - - $(DotNetCli) --roll-forward latestmajor $(XunitConsoleRunner) @(TestAssemblies->'%(Identity)', ' ') $(XunitArgs) - - - + + + + + + + + + + + + Pass + Fail + + + + + + + <_StandaloneProjectsToBuild Include="$(MSBuildThisFileFullPath)"> + StandaloneRunnerScript=%(StandaloneRunnerScripts.Identity) + + + + + + + + - + diff --git a/src/tests/Directory.Build.props b/src/tests/Directory.Build.props index 1822cbe9f31195..6300b45186723f 100644 --- a/src/tests/Directory.Build.props +++ b/src/tests/Directory.Build.props @@ -88,12 +88,6 @@ false - false - true - - false - true - false true llvmaot diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 74c89c69514ca5..4908a1c2198e87 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -310,7 +310,7 @@ - + @@ -421,6 +421,10 @@ + + + StandaloneTestRunner + MergedTestAssembly diff --git a/src/tests/build.cmd b/src/tests/build.cmd index 73691be70ac458..b907eadb38f720 100644 --- a/src/tests/build.cmd +++ b/src/tests/build.cmd @@ -44,8 +44,6 @@ set __BuildLogRootName=TestBuild set __SkipRestorePackages=0 set __SkipManaged= -set __SkipTestWrappers= -set __BuildTestWrappersOnly= set __SkipNative= set __CompositeBuildMode= set __TestBuildMode= @@ -104,12 +102,10 @@ if /i "%arg%" == "Rebuild" (set __RebuildTests=1&set processedArgs if /i "%arg%" == "SkipRestorePackages" (set __SkipRestorePackages=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "SkipManaged" (set __SkipManaged=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "SkipNative" (set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%arg%" == "SkipTestWrappers" (set __SkipTestWrappers=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "SkipGenerateLayout" (set __SkipGenerateLayout=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%arg%" == "CopyNativeOnly" (set __CopyNativeTestBinaries=1&set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set __SkipGenerateLayout=1&set __SkipTestWrappers=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%arg%" == "CopyNativeOnly" (set __CopyNativeTestBinaries=1&set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set __SkipGenerateLayout=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "GenerateLayoutOnly" (set __GenerateLayoutOnly=1&set __SkipManaged=1&set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%arg%" == "BuildTestWrappersOnly" (set __SkipNative=1&set __SkipManaged=1&set __BuildTestWrappersOnly=1&set __SkipGenerateLayout=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "MSBuild" (set __Ninja=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "crossgen2" (set __TestBuildMode=crossgen2&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%arg%" == "composite" (set __CompositeBuildMode=1&set __TestBuildMode=crossgen2&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) @@ -169,8 +165,6 @@ if defined __TestArgParsing ( echo.__BuildLogRootName=%__BuildLogRootName% echo.__SkipRestorePackages=%__SkipRestorePackages% echo.__SkipManaged=%__SkipManaged% - echo.__SkipTestWrappers=%__SkipTestWrappers% - echo.__BuildTestWrappersOnly=%__BuildTestWrappersOnly% echo.__SkipNative=%__SkipNative% echo.__CompositeBuildMode=%__CompositeBuildMode% echo.__TestBuildMode=%__TestBuildMode% @@ -260,7 +254,6 @@ REM === REM ========================================================================================= if "%__SkipNative%" == "1" goto skipnative -if "%__BuildTestWrappersOnly%" == "1" goto skipnative if "%__GenerateLayoutOnly%" == "1" goto skipnative if "%__CopyNativeTestBinaries%" == "1" goto skipnative @@ -386,12 +379,10 @@ echo -Rebuild: Clean up all test artifacts prior to building tests. echo -SkipRestorePackages: Skip package restore. echo -SkipManaged: Skip the managed tests build. echo -SkipNative: Skip the native tests build. -echo -SkipTestWrappers: Skip generating test wrappers. echo -SkipGenerateLayout: Skip generating the Core_Root layout. echo. echo -CopyNativeOnly: Only copy the native test binaries to the managed output. Do not build the native or managed tests. echo -GenerateLayoutOnly: Only generate the Core_Root layout without building managed or native test components. -echo -BuildTestWrappersOnly: Only generate test wrappers without building managed or native test components or generating layouts. echo -MSBuild: Use MSBuild instead of Ninja. echo -Crossgen2: Precompiles the framework managed assemblies in coreroot using the Crossgen2 compiler. echo -Composite: Use Crossgen2 composite mode (all framework gets compiled into a single native R2R library). diff --git a/src/tests/build.proj b/src/tests/build.proj index d212180561060d..baeac24e0263a7 100644 --- a/src/tests/build.proj +++ b/src/tests/build.proj @@ -2,7 +2,6 @@ - @@ -47,73 +46,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @(LegacyRunnableTestPaths->Count()) - - - - - - - - - - - - False True - + - @@ -202,178 +149,6 @@ /> - - - - - - android-$(TargetArchitecture) - $([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($(_CMDDIR))))) - $([System.String]::Copy('$(_CMDDIR)').Replace("$(CMDDIR_Grandparent)/","")) - $([System.String]::Copy('$(CategoryWithSlash)').Replace('/','_')) - $(IntermediateOutputPath)\AndroidApps\$(Category) - $(BuildDir)\apk - $(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).apk - 127.0.0.1:9000,nosuspend,listen - true - $(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)\runtimes\android-$(TargetArchitecture)\ - arm64-v8a - armeabi-v7a - x86_64 - x86 - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_RuntimeFlavor>Mono - <_RuntimeFlavor Condition="'$(UseMonoRuntime)' == 'false' and '$(TestBuildMode)' != 'nativeaot'">CoreCLR - <_RuntimeFlavor Condition="'$(TestBuildMode)' == 'nativeaot'">nativeaot - - - - - - - - - - - - - - - - - - - - - - $([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($(_CMDDIR))))) - $([System.IO.Path]::GetFileName($(CategoryPath))) - $(CategoryName)/$([System.String]::Copy('$(_CMDDIR)').Replace("$(CategoryPath)/","")) - $([System.IO.Path]::GetFileName($(_CMDDIR))) - $([System.String]::Copy('$(TestRelativePath)').Replace('/','_')) - $(IntermediateOutputPath)\iOSApps\$(AppName) - $(XUnitTestBinBase)$(TestRelativePath)\$(AppName).app - - - - - - true - true - $(AppDir) - false - false - - - - - - - - - - - <_LinkerFlagsToDrop Include="@(NativeFramework->'-framework %(Identity)')" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - _CMDDIR=%(TestDirectories.Identity) - - - - - - @@ -396,8 +171,10 @@ @(MergedAssemblyMarkerPaths->'%(RootDir)%(Directory)$(MobileAppBundleDirName)/**/*.$(TestScriptExtension)') - + + @@ -445,7 +222,7 @@ + Condition="'$(__GenerateLayoutOnly)' != '1' and '$(__SkipManaged)' != '1' and !$(MonoAot) and !$(MonoFullAot)" /> @@ -517,14 +294,10 @@ - - + Condition="'$(__CopyNativeTestBinaries)' != '1' and '$(__SkipGenerateLayout)' != '1' and !$(MonoAot) and !$(MonoFullAot)"> - - + Condition="'$(__CopyNativeTestBinaries)' != '1' and '$(__TestBuildMode)' == 'crossgen2' and !$(MonoAot) and !$(MonoFullAot)" > $(__TestIntermediatesDir)\crossgen.out @@ -582,20 +350,10 @@ - - - - + Condition="'$(__GenerateLayoutOnly)' != '1' and '$(__CopyNativeTestBinaries)' != '1' and ($(MonoAot) or $(MonoFullAot))" /> diff --git a/src/tests/build.sh b/src/tests/build.sh index 1a556196b24902..e7f06db58d4fbf 100755 --- a/src/tests/build.sh +++ b/src/tests/build.sh @@ -59,7 +59,7 @@ build_Tests() MSBUILDDEBUGPATH="${__MsbuildDebugLogsDir}" export MSBUILDDEBUGPATH - if [[ "$__SkipNative" != 1 && "$__BuildTestWrappersOnly" != 1 && "$__GenerateLayoutOnly" != 1 && "$__CopyNativeTestBinaries" != 1 && \ + if [[ "$__SkipNative" != 1 && "$__GenerateLayoutOnly" != 1 && "$__CopyNativeTestBinaries" != 1 && \ "$__TargetOS" != "android" && "$__TargetOS" != "ios" && "$__TargetOS" != "iossimulator" && "$__TargetOS" != "tvos" && "$__TargetOS" != "tvossimulator" ]]; then build_native "$__TargetOS" "$__TargetArch" "$__TestDir" "$__NativeTestIntermediatesDir" "install" "$__CMakeArgs" "CoreCLR test component" @@ -91,7 +91,6 @@ build_Tests() export __SkipManaged export __SkipRestorePackages export __SkipGenerateLayout - export __SkipTestWrappers export __BuildTestProject export __BuildTestDir export __BuildTestTree @@ -101,7 +100,6 @@ build_Tests() export __Priority export __CreatePerfmap export __CompositeBuildMode - export __BuildTestWrappersOnly export __GenerateLayoutOnly export __TestBuildMode export __MonoAot @@ -144,12 +142,10 @@ usage_list+=("-rebuild - Clean up all test artifacts prior to building tests.") usage_list+=("-skiprestorepackages - Skip package restore.") usage_list+=("-skipmanaged - Skip the managed tests build.") usage_list+=("-skipnative - Skip the native tests build.") -usage_list+=("-skiptestwrappers - Skip generating test wrappers.") usage_list+=("-skipgeneratelayout - Skip generating the Core_Root layout.") usage_list+=("") usage_list+=("-copynativeonly - Only copy the native test binaries to the managed output. Do not build the native or managed tests.") usage_list+=("-generatelayoutonly - Only generate the Core_Root layout without building managed or native test components.") -usage_list+=("-buildtestwrappersonly - Only generate test wrappers without building managed or native test components or generating layouts.") usage_list+=("") usage_list+=("-crossgen2 - Precompiles the framework managed assemblies in coreroot using the Crossgen2 compiler.") usage_list+=("-composite - Use Crossgen2 composite mode (all framework gets compiled into a single native R2R library).") @@ -181,7 +177,6 @@ handle_arguments_local() { case "$opt" in skipmanaged|-skipmanaged) __SkipManaged=1 - __BuildTestWrappers=0 ;; skipnative|-skipnative) @@ -189,20 +184,11 @@ handle_arguments_local() { __CopyNativeProjectsAfterCombinedTestBuild=false ;; - buildtestwrappersonly|-buildtestwrappersonly) - __BuildTestWrappersOnly=1 - ;; - - skiptestwrappers|-skiptestwrappers) - __SkipTestWrappers=1 - ;; - copynativeonly|-copynativeonly) __SkipNative=1 __CopyNativeTestBinaries=1 __CopyNativeProjectsAfterCombinedTestBuild=false __SkipGenerateLayout=1 - __SkipTestWrappers=1 ;; crossgen2|-crossgen2) @@ -334,8 +320,6 @@ __IncludeTests=INCLUDE_TESTS __ProjectDir="$__ProjectRoot" export __ProjectDir -__SkipTestWrappers=0 -__BuildTestWrappersOnly=0 __Compiler=clang __ConfigureOnly=0 __CopyNativeProjectsAfterCombinedTestBuild=true diff --git a/src/tests/run.py b/src/tests/run.py index a8706ac7a9ab80..9a6a0ada957b58 100755 --- a/src/tests/run.py +++ b/src/tests/run.py @@ -1210,11 +1210,77 @@ def parse_test_results(args, tests, assemblies): if item_lower.endswith(".testrun.xml"): item_name = item[:-len(".testrun.xml")] parse_test_results_xml_file(args, item, item_name, tests, assemblies) + elif item_lower == "standalonerunnertestresults.testrun.log": + found = True + parse_standalone_runner_results_file(args, item, tests, assemblies) if not found: - print("Unable to find testRun.xml. This normally means the tests did not run.") + print("Unable to find testRun.xml or StandaloneRunnerTestResults.testrun.log. This normally means the tests did not run.") print("It could also mean there was a problem logging. Please run the tests again.") +def parse_standalone_runner_results_file(args, item, tests, assemblies): + """ Parse test results from a standalone runner results log file + + Args: + args : arguments + item : log filename in the logs directory + tests : list of individual test results (filled in by this function) + assemblies : dictionary of per-assembly aggregations (filled in by this function) + """ + + log_result_file = os.path.join(args.logs_dir, item) + print("Analyzing {}".format(log_result_file)) + + assembly_name = "StandaloneRunnerTests" + assembly_info = assemblies[assembly_name] + if assembly_info is None: + assembly_info = defaultdict(lambda: None, { + "name": assembly_name, + "display_name": assembly_name, + "is_merged_tests_run": False, + "time": 0.0, + "passed": 0, + "failed": 0, + "skipped": 0, + }) + + with open(log_result_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line: + continue + + # Format is: ": " where Result is "Pass" or "Fail" + last_colon_idx = line.rfind(': ') + if last_colon_idx == -1: + continue + + script_path = line[:last_colon_idx] + result = line[last_colon_idx + 2:] + + # Derive a test name from the script path + test_name = os.path.basename(script_path) + test_name_without_ext = os.path.splitext(test_name)[0] + + tests.append(defaultdict(lambda: None, { + "name": test_name_without_ext, + "test_path": script_path, + "result": result, + "time": 0.0, + "test_output": None, + "assembly_display_name": assembly_name, + "is_merged": False + })) + + if result == "Pass": + assembly_info["passed"] += 1 + elif result == "Fail": + assembly_info["failed"] += 1 + else: + assembly_info["skipped"] += 1 + + assemblies[assembly_name] = assembly_info + def parse_test_results_xml_file(args, item, item_name, tests, assemblies): """ Parse test results from a single xml results file @@ -1245,19 +1311,12 @@ def parse_test_results_xml_file(args, item, item_name, tests, assemblies): if len(item_name) > 0: display_name = item_name - # Is the results XML from a merged tests model run? - assembly_is_merged_tests_run = False - assembly_test_framework = assembly.attrib["test-framework"] - # Non-merged tests give something like "xUnit.net 2.5.3.0" - if assembly_test_framework == "XUnitWrapperGenerator-generated-runner": - assembly_is_merged_tests_run = True - assembly_info = assemblies[assembly_name] if assembly_info is None: assembly_info = defaultdict(lambda: None, { "name": assembly_name, "display_name": display_name, - "is_merged_tests_run" : assembly_is_merged_tests_run, + "is_merged_tests_run" : True, "time": 0.0, "passed": 0, "failed": 0, @@ -1293,23 +1352,15 @@ def parse_test_results_xml_file(args, item, item_name, tests, assemblies): test_name += " (" + name + ")" result = test.attrib["result"] time = float(collection.attrib["time"]) - if assembly_is_merged_tests_run: - # REVIEW: Even if the test is a .dll file or .CMD file and is found, we don't know how to - # build a repro case with it. - test_location_on_filesystem = None - else: - test_location_on_filesystem = find_test_from_name(args.host_os, args.test_location, name) - if test_location_on_filesystem is None or not os.path.isfile(test_location_on_filesystem): - test_location_on_filesystem = None test_output = test.findtext("output") tests.append(defaultdict(lambda: None, { "name": test_name, - "test_path": test_location_on_filesystem, + "test_path": None, "result" : result, "time": time, "test_output": test_output, "assembly_display_name": display_name, - "is_merged": assembly_is_merged_tests_run + "is_merged": True })) if result == "Pass": assembly_info["passed"] += 1 diff --git a/src/tests/xunit-wrappers.targets b/src/tests/xunit-wrappers.targets deleted file mode 100644 index 9be469d94e447f..00000000000000 --- a/src/tests/xunit-wrappers.targets +++ /dev/null @@ -1,506 +0,0 @@ - - - - - - - - - - <_XunitWrapperGen > - - - - - - - - - $(XUnitTestBinBase)\$(CategoryWithSlash) - $(MicrosoftNETCoreAppRefVersion) - false - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $([System.IO.Path]::GetRelativePath('$(XunitTestBinBase)', '$(_CMDDIR)')) - $([System.String]::Copy('$(CategoryWithSlash)').Replace('\','.')) - $([System.String]::Copy('$(CategoryWithSlash)').Replace('/','.')) - $(Category).XUnitWrapper - $(XunitWrapperGeneratedCSDirBase)$(Category) - android - apple - false - true - - - - <_XunitProlog Condition=" '$(_XunitProlog)'=='' and '$(IsMobile)'=='false' "> - - - <_XunitProlog Condition=" '$(_XunitProlog)'=='' and '$(IsMobile)'=='true' "> - - { - // This class has no code, and is never created. Its purpose is simply - // to be the place to apply [CollectionDefinition] and all the - // ICollectionFixture<> interfaces. - } -]]> - - <_XunitEpilog Condition=" '$(_XunitEpilog)'=='' "> - - - - - - testExecutable = testExecutable.Replace("\\", "/")%3B - - - - - - - - - - - - - - - $(Category) - - - - $([MSBuild]::MakeRelative($(_CMDDIR), %(FullPath))) - - - $([MSBuild]::MakeRelative($(XunitTestBinBase), %(AllCMDs.FullPath))) - _$([MSBuild]::ValueOrDefault(%(AllCMDs.RelativeToCMDDIR),"").Replace(".","_").Replace("\","_").Replace("-","_")) - _$([MSBuild]::ValueOrDefault(%(AllCMDs.RelativeToCMDDIR),"").Replace($(TestScriptExtension),"").Replace(".","_").Replace("\","_").Replace("-","_")) - _$([MSBuild]::ValueOrDefault(%(AllCMDs.RelativeToCMDDIR),"").Replace(".","_").Replace("/","_").Replace("-","_")) - _$([MSBuild]::ValueOrDefault(%(AllCMDs.RelativeToCMDDIR), '').Replace($(TestScriptExtension),'').Replace('.','_').Replace('/','_').Replace('-','_')) - %(AllCMDs.TestGroup) - - testOutput = new List()%3B - - try - { - testOutput.AddRange(System.IO.File.ReadAllLines(errorFile))%3B - } - catch (Exception ex) - { - testOutput.Add("Unable to read error file: " + errorFile)%3B - testOutput.Add(ex.ToString())%3B - } - - testOutput.Add(string.Empty)%3B - testOutput.Add("Return code: " + ret)%3B - testOutput.Add("Raw output file: " + outputFile)%3B - testOutput.Add("Raw output:")%3B - - try - { - testOutput.AddRange(System.IO.File.ReadAllLines(outputFile))%3B - } - catch(Exception ex) - { - testOutput.Add("Unable to read output file: " + outputFile)%3B - testOutput.Add(ex.ToString())%3B - } - - testOutput.Add("To run the test:")%3B - testOutput.Add("> set CORE_ROOT=" + _Global.coreRoot)%3B - testOutput.Add("> " + testExecutable)%3B - - var unicodeControlCharsRegex = new Regex("%5C%5Cp{C}+")%3B - - // Remove all characters that have no visual or spatial representation. - for (int i = 0%3B i < testOutput.Count%3B i++) - { - string line = testOutput[i]%3B - line = unicodeControlCharsRegex.Replace(line, string.Empty)%3B - testOutput[i] = line%3B - } - - foreach (string line in testOutput) - { - output.WriteLine(line)%3B - } - - Assert.True(ret == CoreclrTestWrapperLib.EXIT_SUCCESS_CODE, string.Join(Environment.NewLine, testOutput))%3B - } - } - } - - ]]> - - - testOutput = new List()%3B - - try - { - testOutput.AddRange(System.IO.File.ReadAllLines(errorFile))%3B - } - catch (Exception ex) - { - testOutput.Add("Unable to read error file: " + errorFile)%3B - testOutput.Add(ex.ToString())%3B - } - - testOutput.Add(string.Empty)%3B - testOutput.Add("Return code: " + ret)%3B - testOutput.Add("Raw output file: " + outputFile)%3B - testOutput.Add("Raw output:")%3B - - try - { - testOutput.AddRange(System.IO.File.ReadAllLines(outputFile))%3B - } - catch(Exception ex) - { - testOutput.Add("Unable to read output file: " + outputFile)%3B - testOutput.Add(ex.ToString())%3B - } - - testOutput.Add("To run the test:")%3B - testOutput.Add("> set CORE_ROOT=" + globalVar.coreRoot)%3B - testOutput.Add("> " + testExecutable)%3B - - var unicodeControlCharsRegex = new Regex("%5C%5Cp{C}+")%3B - - // Remove all characters that have no visual or spatial representation. - for (int i = 0%3B i < testOutput.Count%3B i++) - { - string line = testOutput[i]%3B - line = unicodeControlCharsRegex.Replace(line, string.Empty)%3B - testOutput[i] = line%3B - } - - foreach (string line in testOutput) - { - output.WriteLine(line)%3B - } - - // Add Android app running log to testOutput - if (ret != CoreclrTestWrapperLib.EXIT_SUCCESS_CODE) - { - string androidLogFile = System.IO.Path.Combine(outputDir, "adb-logcat-net.dot." + globalVar.category + "-net.dot.MonoRunner.log")%3B - if(File.Exists(androidLogFile)) - { - testOutput.AddRange(System.IO.File.ReadAllLines(androidLogFile))%3B - } - } - - Assert.True(ret == CoreclrTestWrapperLib.EXIT_SUCCESS_CODE, string.Join(Environment.NewLine, testOutput))%3B - } - } - } - ]]> - - - - - -