Ejemplo n.º 1
0
        //we anticipate source paths like this:
        //InitialSource:        <DebuggeeSourceRoot>/<DebuggeeName>
        //DebuggeeNativeLibDir: <DebuggeeNativeLibRoot>/<DebuggeeName>
        //DotNetRootBuildDir:   <DebuggeeBuildRoot>
        //DebuggeeSolutionDir:  <DebuggeeBuildRoot>/<DebuggeeName>
        //DebuggeeProjectDir:   <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]
        //DebuggeeBinaryDir:    <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]
        //DebuggeeBinaryDll:    <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/<DebuggeeName>.dll
        //DebuggeeBinaryExe:    <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]/<DebuggeeName>.exe
        //LogPath:              <DebuggeeBuildRoot>/<DebuggeeName>.txt

        // When the runtime directory is present it will have a native host exe in it that has been renamed to the debugee
        // name. It also has a managed dll in it which functions as a managed exe when renamed.
        // When the runtime directory is missing, the framework directory will have a managed dll in it that functions if it
        // is renamed to an exe. I'm sure that renaming isn't the intended usage, but it works and produces less churn
        // in our tests for the moment.
        public abstract DotNetBuildDebuggeeTestStep ConfigureDotNetBuildDebuggeeTask(TestConfiguration config, string dotNetPath, string cliToolsVersion, string debuggeeName);
Ejemplo n.º 2
0
 /// <summary>
 /// Creates a new BaseDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build debuggees via dotnet build.
 /// </summary>
 /// <param name="config">
 /// The test configuration that will be used to configure the build. The following configuration options should be set in the config:
 ///   CliPath                                  The location to get the CLI tools from, either as a .zip/.tar.gz at a web endpoint, a .zip/.tar.gz
 ///                                            at a local filesystem path, or the dotnet binary at a local filesystem path
 ///   WorkingDir                               Temporary storage for CLI tools compressed archives downloaded from the internet will be stored here
 ///   CliCacheRoot                             The final CLI tools will be expanded and cached here
 ///   DebuggeeSourceRoot                       Debuggee sources and template project file will be retrieved from here
 ///   DebuggeeNativeLibRoot                    Debuggee native binary dependencies will be retrieved from here
 ///   DebuggeeBuildRoot                        Debuggee final sources/project file/binary outputs will be placed here
 ///   BuildProjectRuntime                      The runtime moniker to be built
 ///   BuildProjectMicrosoftNETCoreAppVersion   The nuget package version of Microsoft.NETCore.App package to build against for debuggees that references this library
 ///   NugetPackageCacheDir                     The directory where NuGet packages are cached during restore
 ///   NugetFeeds                               The set of nuget feeds that are used to search for packages during restore
 /// </param>
 /// <param name="debuggeeName">
 ///   The name of the debuggee to be built, from which various build file paths are constructed. Before build it is assumed that:
 ///     Debuggee sources are located at               config.DebuggeeSourceRoot/debuggeeName/
 ///     Debuggee native dependencies are located at   config.DebuggeeNativeLibRoot/debuggeeName/
 ///
 ///   After the build:
 ///     Debuggee build outputs will be created at     config.DebuggeeNativeLibRoot/debuggeeName/
 ///     A log of the build is stored at               config.DebuggeeNativeLibRoot/debuggeeName.txt
 /// </param>
 public BaseDebuggeeCompiler(TestConfiguration config, string debuggeeName)
 {
     _acquireTask       = ConfigureAcquireDotNetTask(config);
     _buildDebuggeeTask = ConfigureDotNetBuildDebuggeeTask(config, _acquireTask.LocalDotNetPath, config.CliVersion, debuggeeName);
 }
Ejemplo n.º 3
0
 protected static string GetRuntime(TestConfiguration config)
 {
     return(config.BuildProjectRuntime);
 }
Ejemplo n.º 4
0
 protected abstract string GetFramework(TestConfiguration config);
Ejemplo n.º 5
0
 protected static string GetDebuggeeBinaryExePath(TestConfiguration config, string debuggeeBinaryDirPath, string debuggeeName)
 {
     return(config.IsDesktop || config.PublishSingleFile ? Path.Combine(debuggeeBinaryDirPath, debuggeeName + (OS.Kind == OSKind.Windows ? ".exe" : "")) : null);
 }
Ejemplo n.º 6
0
 protected static string GetLogPath(TestConfiguration config, string framework, string runtime, string debuggeeName)
 {
     return(Path.Combine(GetDotNetRootBuildDirPath(config), $"{framework}-{runtime ?? "any"}-{debuggeeName}.txt"));
 }
Ejemplo n.º 7
0
 protected static string GetDotNetRootBuildDirPath(TestConfiguration config)
 {
     return(config.DebuggeeBuildRoot);
 }
Ejemplo n.º 8
0
 protected static string GetDebuggeeBinaryDllPath(TestConfiguration config, string debuggeeBinaryDirPath, string debuggeeName)
 {
     return(config.IsNETCore ? Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".dll") : null);
 }
Ejemplo n.º 9
0
 protected override string GetFramework(TestConfiguration config)
 {
     return(config.BuildProjectFramework ?? "netcoreapp3.1");
 }
Ejemplo n.º 10
0
 protected static string GetInitialSourceDirPath(TestConfiguration config, string debuggeeName)
 {
     return(Path.Combine(config.DebuggeeSourceRoot, debuggeeName));
 }
Ejemplo n.º 11
0
 /// <summary>
 /// Creates a new CliDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish.
 /// <param name="config">
 ///   LinkerPackageVersion   If set, this version of the linker package will be used to link the debuggee during publish.
 /// </param>
 /// </summary>
 public CliDebuggeeCompiler(TestConfiguration config, string debuggeeName) : base(config, debuggeeName)
 {
 }
 protected static string GetDebuggeeBinaryExePath(TestConfiguration config, string debuggeeBinaryDirPath, string debuggeeName)
 {
     return(config.IsDesktop ? Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".exe") : null);
 }
Ejemplo n.º 13
0
        protected static string GetLogPath(TestConfiguration config, string framework, string runtime, string debuggeeName)
        {
            string version = config.BuildProjectMicrosoftNetCoreAppVersion;

            return(Path.Combine(GetDotNetRootBuildDirPath(config), $"{framework}-{runtime ?? "any"}-{debuggeeName}.txt"));
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Run debuggee (without any debugger) and compare the console output to the regex specified.
        /// </summary>
        /// <param name="config">test config to use</param>
        /// <param name="output">output helper</param>
        /// <param name="testName">test case name</param>
        /// <param name="debuggeeName">debuggee name (no path)</param>
        /// <param name="outputRegex">regex to match on console (standard and error) output</param>
        /// <returns></returns>
        public static async Task <int> Run(TestConfiguration config, ITestOutputHelper output, string testName, string debuggeeName, string outputRegex)
        {
            OutputHelper outputHelper = null;

            try
            {
                // Setup the logging from the options in the config file
                outputHelper = ConfigureLogging(config, output, testName);

                // Restore and build the debuggee. The debuggee name is lower cased because the
                // source directory name has been lowercased by the build system.
                DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName.ToLowerInvariant(), outputHelper);

                outputHelper.WriteLine("Starting {0}", testName);
                outputHelper.WriteLine("{");

                // Get the full debuggee launch command line (includes the host if required)
                string exePath   = debuggeeConfig.BinaryExePath;
                string arguments = debuggeeConfig.BinaryDirPath;
                if (!string.IsNullOrWhiteSpace(config.HostExe))
                {
                    exePath   = config.HostExe;
                    arguments = Environment.ExpandEnvironmentVariables(string.Format("{0} {1} {2}", config.HostArgs, debuggeeConfig.BinaryExePath, debuggeeConfig.BinaryDirPath));
                }

                TestLogger    testLogger    = new TestLogger(outputHelper.IndentedOutput);
                ProcessRunner processRunner = new ProcessRunner(exePath, arguments).
                                              WithLog(testLogger).
                                              WithTimeout(TimeSpan.FromMinutes(5));

                processRunner.Start();

                // Wait for the debuggee to finish before getting the debuggee output
                int exitCode = await processRunner.WaitForExit();

                string debuggeeStandardOutput = testLogger.GetStandardOutput();
                string debuggeeStandardError  = testLogger.GetStandardError();

                // The debuggee output is all the stdout first and then all the stderr output last
                string debuggeeOutput = debuggeeStandardOutput + debuggeeStandardError;
                if (string.IsNullOrEmpty(debuggeeOutput))
                {
                    throw new Exception("No debuggee output");
                }
                // Remove any CR's in the match string because this assembly is built on Windows (with CRs) and
                // ran on Linux/OS X (without CRs).
                outputRegex = outputRegex.Replace("\r", "");

                // Now match the debuggee output and regex match string
                if (!new Regex(outputRegex, RegexOptions.Multiline).IsMatch(debuggeeOutput))
                {
                    throw new Exception(string.Format("\nDebuggee output:\n\n'{0}'\n\nDid not match the expression:\n\n'{1}'", debuggeeOutput, outputRegex));
                }

                return(exitCode);
            }
            catch (Exception ex)
            {
                // Log the exception
                outputHelper?.WriteLine(ex.ToString());
                throw;
            }
            finally
            {
                outputHelper?.WriteLine("}");
                outputHelper?.Dispose();
            }
        }
Ejemplo n.º 15
0
        public static async Task <int> RemoteInvoke(ITestOutputHelper output, TestConfiguration config, TimeSpan timeout, string dumpPath, Func <string, Task <int> > method)
        {
            RemoteInvokeOptions options = new()
            {
                StartInfo = new ProcessStartInfo()
                {
                    RedirectStandardOutput = true, RedirectStandardError = true
                }
            };
            // The remoteInvokeHandle is NOT disposed (i.e. with a using) here because the RemoteExecutor dispose code uses an older (1.x) version
            // of clrmd that conflicts with the 2.0 version diagnostics is using and throws the exception:
            //
            // "Method not found: 'Microsoft.Diagnostics.Runtime.DataTarget Microsoft.Diagnostics.Runtime.DataTarget.AttachToProcess(Int32, UInt32)'."
            //
            // When RemoteExecutor is fixed the "using" can be added and the GC.SuppressFinalize be removed.
            RemoteInvokeHandle remoteInvokeHandle = RemoteExecutor.Invoke(method, config.Serialize(), options);

            GC.SuppressFinalize(remoteInvokeHandle);
            try
            {
                Task stdOutputTask = WriteStreamToOutput(remoteInvokeHandle.Process.StandardOutput, output);
                Task stdErrorTask  = WriteStreamToOutput(remoteInvokeHandle.Process.StandardError, output);
                Task outputTasks   = Task.WhenAll(stdErrorTask, stdOutputTask);

                Task processExit   = Task.Factory.StartNew(() => remoteInvokeHandle.Process.WaitForExit(), TaskCreationOptions.LongRunning);
                Task timeoutTask   = Task.Delay(timeout);
                Task completedTask = await Task.WhenAny(outputTasks, processExit, timeoutTask);

                if (completedTask == timeoutTask)
                {
                    if (!string.IsNullOrEmpty(dumpPath))
                    {
                        output.WriteLine($"RemoteExecutorHelper.RemoteInvoke timed out: writing dump to {dumpPath}");
                        DiagnosticsClient client = new(remoteInvokeHandle.Process.Id);
                        try
                        {
                            await client.WriteDumpAsync(DumpType.WithHeap, dumpPath, WriteDumpFlags.None, CancellationToken.None);
                        }
                        catch (Exception ex) when(ex is ArgumentException || ex is UnsupportedCommandException || ex is ServerErrorException)
                        {
                            output.WriteLine($"RemoteExecutorHelper.RemoteInvoke: writing dump FAILED {ex}");
                        }
                    }
                    throw new XunitException("RemoteExecutorHelper.RemoteInvoke timed out");
                }
                else
                {
                    return(remoteInvokeHandle.ExitCode);
                }
            }
            finally
            {
                if (remoteInvokeHandle.Process != null)
                {
                    try
                    {
                        output.WriteLine($"RemoteExecutorHelper.RemoteInvoke: killing process {remoteInvokeHandle.Process.Id}");
                        remoteInvokeHandle.Process.Kill(entireProcessTree: true);
                    }
                    catch
                    {
                    }
                    remoteInvokeHandle.Process.Dispose();
                    remoteInvokeHandle.Process = null;
                }
            }
        }