ProcessTestExecutor.cs 5.4 KB
Newer Older
J
Jared Parsons 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RunTests
{
    internal sealed class ProcessTestExecutor : ITestExecutor
    {
        private readonly Options _options;

        internal ProcessTestExecutor(Options options)
        {
            _options = options;
        }

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
        public string GetCommandLine(string assemblyPath)
        {
            return $"{_options.XunitPath} {GetCommandLineArguments(assemblyPath)}";
        }

        public string GetCommandLineArguments(string assemblyPath)
        {
            var assemblyName = Path.GetFileName(assemblyPath);
            var resultsFilePath = GetResultsFilePath(assemblyPath);

            var builder = new StringBuilder();
            builder.AppendFormat(@"""{0}""", assemblyPath);
            builder.AppendFormat(@" -{0} ""{1}""", _options.UseHtml ? "html" : "xml", resultsFilePath);
            builder.Append(" -noshadow -verbose");

            if (!string.IsNullOrWhiteSpace(_options.Trait))
            {
                var traits = _options.Trait.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var trait in traits)
                {
                    builder.AppendFormat(" -trait {0}", trait);
                }
            }

            if (!string.IsNullOrWhiteSpace(_options.NoTrait))
            {
                var traits = _options.NoTrait.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var trait in traits)
                {
                    builder.AppendFormat(" -notrait {0}", trait);
                }
            }

            return builder.ToString();
        }

        private string GetResultsFilePath(string assemblyPath)
        {
            var assemblyName = Path.GetFileName(assemblyPath);
            var resultsDir = Path.Combine(Path.GetDirectoryName(assemblyPath), Constants.ResultsDirectoryName);
            return Path.Combine(resultsDir, $"{assemblyName}.{(_options.UseHtml ? "html" : "xml")}");
        }

J
Jared Parsons 已提交
66
        public async Task<TestResult> RunTestAsync(string assemblyPath, CancellationToken cancellationToken)
J
Jared Parsons 已提交
67 68
        {
            try
69
            {
70 71 72
                var commandLineArguments = GetCommandLineArguments(assemblyPath);
                var resultsFilePath = GetResultsFilePath(assemblyPath);
                var resultsDir = Path.GetDirectoryName(resultsFilePath);
J
Jared Parsons 已提交
73 74 75 76 77 78

                // NOTE: xUnit doesn't always create the log directory
                Directory.CreateDirectory(resultsDir);

                // NOTE: xUnit seems to have an occasional issue creating logs create
                // an empty log just in case, so our runner will still fail.
79
                File.Create(resultsFilePath).Close();
J
Jared Parsons 已提交
80 81 82 83 84

                var start = DateTime.UtcNow;
                var xunitPath = _options.XunitPath;
                var processOutput = await ProcessRunner.RunProcessAsync(
                    xunitPath,
85
                    commandLineArguments,
J
Jared Parsons 已提交
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
                    lowPriority: false,
                    displayWindow: false,
                    captureOutput: true,
                    cancellationToken: cancellationToken).ConfigureAwait(false);
                var span = DateTime.UtcNow - start;

                if (processOutput.ExitCode != 0)
                {
                    // On occasion we get a non-0 output but no actual data in the result file.  The could happen
                    // if xunit manages to crash when running a unit test (a stack overflow could cause this, for instance).
                    // To avoid losing information, write the process output to the console.  In addition, delete the results
                    // file to avoid issues with any tool attempting to interpret the (potentially malformed) text.
                    var resultData = string.Empty;
                    try
                    {
101
                        resultData = File.ReadAllText(resultsFilePath).Trim();
J
Jared Parsons 已提交
102 103 104 105 106 107 108 109 110
                    }
                    catch
                    {
                        // Happens if xunit didn't produce a log file
                    }

                    if (resultData.Length == 0)
                    {
                        // Delete the output file.
111 112
                        File.Delete(resultsFilePath);
                        resultsFilePath = null;
J
Jared Parsons 已提交
113 114 115
                    }
                }

116
                var commandLine = GetCommandLine(assemblyPath);
117 118 119 120 121 122
                var standardOutput = string.Join(Environment.NewLine, processOutput.OutputLines);
                var errorOutput = string.Join(Environment.NewLine, processOutput.ErrorLines);

                return new TestResult(
                    exitCode: processOutput.ExitCode,
                    assemblyPath: assemblyPath,
123
                    resultDir: resultsDir,
124 125 126 127 128
                    resultsFilePath: resultsFilePath,
                    commandLine: commandLine,
                    elapsed: span,
                    standardOutput: standardOutput,
                    errorOutput: errorOutput);
J
Jared Parsons 已提交
129 130 131 132 133 134 135 136
            }
            catch (Exception ex)
            {
                throw new Exception($"Unable to run {assemblyPath} with {_options.XunitPath}. {ex}");
            }
        }
    }
}