PR Feedback
This commit is contained in:
parent
407c933480
commit
a84a56a152
9 changed files with 487 additions and 32 deletions
|
@ -41,7 +41,7 @@ compilers, package managers and other utilities that developers need.",
|
|||
"bin/dotnet-compile" : "usr/bin/dotnet-compile",
|
||||
"bin/dotnet-compile-csc" : "usr/bin/dotnet-compile-csc",
|
||||
"bin/dotnet-compile-native" : "/usr/bin/dotnet-compile-native",
|
||||
"bin/dotnet-init":"usr/bin/dotnet-init",
|
||||
"bin/dotnet-init":"usr/bin/dotnet-init",
|
||||
"bin/dotnet-publish" : "usr/bin/dotnet-publish",
|
||||
"bin/dotnet-repl" : "usr/bin/dotnet-repl",
|
||||
"bin/dotnet-repl-csi" : "usr/bin/dotnet-repl-csi",
|
||||
|
|
|
@ -99,5 +99,28 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
.Build();
|
||||
}
|
||||
}
|
||||
public string GetAssemblyPath(string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
GetOutputDirectoryPath(buildConfiguration),
|
||||
ProjectFile.Name + FileNameSuffixes.DotNet.DynamicLib);
|
||||
}
|
||||
|
||||
public string GetPdbPath(string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
GetOutputDirectoryPath(buildConfiguration),
|
||||
ProjectFile.Name + FileNameSuffixes.DotNet.ProgramDatabase);
|
||||
}
|
||||
|
||||
private string GetOutputDirectoryPath(string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
ProjectDirectory,
|
||||
DirectoryNames.Bin,
|
||||
buildConfiguration,
|
||||
TargetFramework.GetShortFolderName(),
|
||||
ProjectModel.RuntimeIdentifier.Current);
|
||||
}
|
||||
}
|
||||
}
|
305
src/Microsoft.DotNet.Tools.Test/Program.cs
Normal file
305
src/Microsoft.DotNet.Tools.Test/Program.cs
Normal file
|
@ -0,0 +1,305 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.Extensions.Testing.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
DebugHelper.HandleDebugSwitch(ref args);
|
||||
|
||||
var app = new CommandLineApplication(false)
|
||||
{
|
||||
Name = "dotnet test",
|
||||
FullName = ".NET Test Driver",
|
||||
Description = "Test Driver for the .NET Platform"
|
||||
};
|
||||
|
||||
app.HelpOption("-?|-h|--help");
|
||||
|
||||
var parentProcessIdOption = app.Option("--parentProcessId", "Used by IDEs to specify their process ID. Test will exit if the parent process does.", CommandOptionType.SingleValue);
|
||||
var portOption = app.Option("--port", "Used by IDEs to specify a port number to listen for a connection.", CommandOptionType.SingleValue);
|
||||
var projectPath = app.Argument("<PROJECT>", "The project to test, defaults to the current directory. Can be a path to a project.json or a project directory.");
|
||||
|
||||
app.OnExecute(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register for parent process's exit event
|
||||
if (parentProcessIdOption.HasValue())
|
||||
{
|
||||
int processId;
|
||||
|
||||
if (!Int32.TryParse(parentProcessIdOption.Value(), out processId))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid process id '{parentProcessIdOption.Value()}'. Process id must be an integer.");
|
||||
}
|
||||
|
||||
RegisterForParentProcessExit(processId);
|
||||
}
|
||||
|
||||
var projectContexts = CreateProjectContexts(projectPath.Value);
|
||||
|
||||
var projectContext = projectContexts.First();
|
||||
|
||||
var testRunner = projectContext.ProjectFile.TestRunner;
|
||||
|
||||
if (portOption.HasValue())
|
||||
{
|
||||
int port;
|
||||
|
||||
if (!Int32.TryParse(portOption.Value(), out port))
|
||||
{
|
||||
throw new InvalidOperationException($"{portOption.Value()} is not a valid port number.");
|
||||
}
|
||||
|
||||
return RunDesignTime(port, projectContext, testRunner);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RunConsole(projectContext, app, testRunner);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
|
||||
return -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
|
||||
return -2;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return app.Execute(args);
|
||||
}
|
||||
|
||||
private static int RunConsole(ProjectContext projectContext, CommandLineApplication app, string testRunner)
|
||||
{
|
||||
var commandArgs = new List<string> {projectContext.GetAssemblyPath(Constants.DefaultConfiguration)};
|
||||
commandArgs.AddRange(app.RemainingArguments);
|
||||
|
||||
return Command.Create($"{GetCommandName(testRunner)}", commandArgs, projectContext.TargetFramework)
|
||||
.ForwardStdErr()
|
||||
.ForwardStdOut()
|
||||
.Execute()
|
||||
.ExitCode;
|
||||
}
|
||||
|
||||
private static int RunDesignTime(int port, ProjectContext projectContext, string testRunner)
|
||||
{
|
||||
Console.WriteLine("Listening on port {0}", port);
|
||||
using (var channel = ReportingChannel.ListenOn(port))
|
||||
{
|
||||
Console.WriteLine("Client accepted {0}", channel.Socket.LocalEndPoint);
|
||||
|
||||
HandleDesignTimeMessages(projectContext, testRunner, channel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleDesignTimeMessages(ProjectContext projectContext, string testRunner, ReportingChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = channel.ReadQueue.Take();
|
||||
|
||||
if (message.MessageType == "ProtocolVersion")
|
||||
{
|
||||
HandleProtocolVersionMessage(message, channel);
|
||||
|
||||
// Take the next message, which should be the command to execute.
|
||||
message = channel.ReadQueue.Take();
|
||||
}
|
||||
|
||||
if (message.MessageType == "TestDiscovery.Start")
|
||||
{
|
||||
HandleTestDiscoveryStartMessage(testRunner, channel, projectContext);
|
||||
}
|
||||
else if (message.MessageType == "TestExecution.Start")
|
||||
{
|
||||
HandleTestExecutionStartMessage(testRunner, message, channel, projectContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleUnknownMessage(message, channel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
channel.SendError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleProtocolVersionMessage(Message message, ReportingChannel channel)
|
||||
{
|
||||
var version = message.Payload?.ToObject<ProtocolVersionMessage>().Version;
|
||||
var supportedVersion = 1;
|
||||
TestHostTracing.Source.TraceInformation(
|
||||
"[ReportingChannel]: Requested Version: {0} - Using Version: {1}",
|
||||
version,
|
||||
supportedVersion);
|
||||
|
||||
channel.Send(new Message()
|
||||
{
|
||||
MessageType = "ProtocolVersion",
|
||||
Payload = JToken.FromObject(new ProtocolVersionMessage()
|
||||
{
|
||||
Version = supportedVersion,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private static void HandleTestDiscoveryStartMessage(string testRunner, ReportingChannel channel, ProjectContext projectContext)
|
||||
{
|
||||
TestHostTracing.Source.TraceInformation("Starting Discovery");
|
||||
|
||||
var commandArgs = new List<string> { projectContext.GetAssemblyPath(Constants.DefaultConfiguration) };
|
||||
|
||||
commandArgs.AddRange(new[]
|
||||
{
|
||||
"--list",
|
||||
"--designtime"
|
||||
});
|
||||
|
||||
ExecuteRunnerCommand(testRunner, channel, commandArgs);
|
||||
|
||||
channel.Send(new Message()
|
||||
{
|
||||
MessageType = "TestDiscovery.Response",
|
||||
});
|
||||
|
||||
TestHostTracing.Source.TraceInformation("Completed Discovery");
|
||||
}
|
||||
|
||||
private static void HandleTestExecutionStartMessage(string testRunner, Message message, ReportingChannel channel, ProjectContext projectContext)
|
||||
{
|
||||
TestHostTracing.Source.TraceInformation("Starting Execution");
|
||||
|
||||
var commandArgs = new List<string> { projectContext.GetAssemblyPath(Constants.DefaultConfiguration) };
|
||||
|
||||
commandArgs.AddRange(new[]
|
||||
{
|
||||
"--designtime"
|
||||
});
|
||||
|
||||
var tests = message.Payload?.ToObject<RunTestsMessage>().Tests;
|
||||
if (tests != null)
|
||||
{
|
||||
foreach (var test in tests)
|
||||
{
|
||||
commandArgs.Add("--test");
|
||||
commandArgs.Add(test);
|
||||
}
|
||||
}
|
||||
|
||||
ExecuteRunnerCommand(testRunner, channel, commandArgs);
|
||||
|
||||
channel.Send(new Message()
|
||||
{
|
||||
MessageType = "TestExecution.Response",
|
||||
});
|
||||
|
||||
TestHostTracing.Source.TraceInformation("Completed Execution");
|
||||
}
|
||||
|
||||
private static void HandleUnknownMessage(Message message, ReportingChannel channel)
|
||||
{
|
||||
var error = string.Format("Unexpected message type: '{0}'.", message.MessageType);
|
||||
|
||||
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, error);
|
||||
|
||||
channel.SendError(error);
|
||||
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
|
||||
private static void ExecuteRunnerCommand(string testRunner, ReportingChannel channel, List<string> commandArgs)
|
||||
{
|
||||
var result = Command.Create(GetCommandName(testRunner), commandArgs, new NuGetFramework("DNXCore", Version.Parse("5.0")))
|
||||
.OnOutputLine(line =>
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.Send(JsonConvert.DeserializeObject<Message>(line));
|
||||
}
|
||||
catch
|
||||
{
|
||||
TestHostTracing.Source.TraceInformation(line);
|
||||
}
|
||||
})
|
||||
.Execute();
|
||||
|
||||
if (result.ExitCode != 0)
|
||||
{
|
||||
channel.SendError($"{GetCommandName(testRunner)} returned '{result.ExitCode}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCommandName(string testRunner)
|
||||
{
|
||||
return $"dotnet-test-{testRunner}";
|
||||
}
|
||||
|
||||
private static void RegisterForParentProcessExit(int id)
|
||||
{
|
||||
var parentProcess = Process.GetProcesses().FirstOrDefault(p => p.Id == id);
|
||||
|
||||
if (parentProcess != null)
|
||||
{
|
||||
parentProcess.EnableRaisingEvents = true;
|
||||
parentProcess.Exited += (sender, eventArgs) =>
|
||||
{
|
||||
TestHostTracing.Source.TraceEvent(
|
||||
TraceEventType.Information,
|
||||
0,
|
||||
"Killing the current process as parent process has exited.");
|
||||
|
||||
Process.GetCurrentProcess().Kill();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
TestHostTracing.Source.TraceEvent(
|
||||
TraceEventType.Information,
|
||||
0,
|
||||
"Failed to register for parent process's exit event. " +
|
||||
$"Parent process with id '{id}' was not found.");
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ProjectContext> CreateProjectContexts(string projectPath)
|
||||
{
|
||||
projectPath = projectPath ?? Directory.GetCurrentDirectory();
|
||||
|
||||
if (!projectPath.EndsWith(Project.FileName))
|
||||
{
|
||||
projectPath = Path.Combine(projectPath, Project.FileName);
|
||||
}
|
||||
|
||||
if (!File.Exists(projectPath))
|
||||
{
|
||||
throw new InvalidOperationException($"{projectPath} does not exist.");
|
||||
}
|
||||
|
||||
return ProjectContext.CreateContextForEachFramework(projectPath);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace dia2
|
||||
{
|
||||
public enum DataKind
|
||||
{
|
||||
DataIsUnknown,
|
||||
DataIsLocal,
|
||||
DataIsStaticLocal,
|
||||
DataIsParam,
|
||||
DataIsObjectPtr,
|
||||
DataIsFileStatic,
|
||||
DataIsGlobal,
|
||||
DataIsMember,
|
||||
DataIsStaticMember,
|
||||
DataIsConstant
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Extensions.Testing.Abstractions
|
||||
{
|
||||
public interface ITestExecutionSink
|
||||
{
|
||||
void SendTestStarted(Test test);
|
||||
|
||||
void SendTestResult(TestResult testResult);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Extensions.Testing.Abstractions
|
||||
{
|
||||
class LineDelimitedJsonStream
|
||||
{
|
||||
private readonly StreamWriter _stream;
|
||||
|
||||
public LineDelimitedJsonStream(Stream stream)
|
||||
{
|
||||
_stream = new StreamWriter(stream);
|
||||
}
|
||||
|
||||
public void Send(object @object)
|
||||
{
|
||||
_stream.WriteLine(JsonConvert.SerializeObject(@object));
|
||||
|
||||
_stream.Flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System.IO;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace Microsoft.Extensions.Testing.Abstractions
|
||||
{
|
||||
public static class ProjectContextExtensions
|
||||
{
|
||||
public static string AssemblyPath(this ProjectContext projectContext, string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
projectContext.OutputDirectoryPath(buildConfiguration),
|
||||
projectContext.ProjectFile.Name + FileNameSuffixes.DotNet.DynamicLib);
|
||||
}
|
||||
public static string PdbPath(this ProjectContext projectContext, string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
projectContext.OutputDirectoryPath(buildConfiguration),
|
||||
projectContext.ProjectFile.Name + FileNameSuffixes.DotNet.ProgramDatabase);
|
||||
}
|
||||
|
||||
private static string OutputDirectoryPath(this ProjectContext projectContext, string buildConfiguration)
|
||||
{
|
||||
return Path.Combine(
|
||||
projectContext.ProjectDirectory,
|
||||
DirectoryNames.Bin,
|
||||
buildConfiguration,
|
||||
projectContext.TargetFramework.GetShortFolderName(),
|
||||
RuntimeIdentifier.Current);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.Testing.Abstractions
|
||||
{
|
||||
public class StreamingTestDiscoverySink : ITestDiscoverySink
|
||||
{
|
||||
private readonly LineDelimitedJsonStream _stream;
|
||||
|
||||
public StreamingTestDiscoverySink(Stream stream)
|
||||
{
|
||||
_stream = new LineDelimitedJsonStream(stream);
|
||||
}
|
||||
|
||||
public void SendTestFound(Test test)
|
||||
{
|
||||
if (test == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(test));
|
||||
}
|
||||
|
||||
_stream.Send(new Message
|
||||
{
|
||||
MessageType = "TestDiscovery.TestFound",
|
||||
Payload = JToken.FromObject(test),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.Testing.Abstractions
|
||||
{
|
||||
public class StreamingTestExecutionSink : ITestExecutionSink
|
||||
{
|
||||
private readonly LineDelimitedJsonStream _stream;
|
||||
private readonly ConcurrentDictionary<string, TestState> _runningTests;
|
||||
|
||||
|
||||
public StreamingTestExecutionSink(Stream stream)
|
||||
{
|
||||
_stream = new LineDelimitedJsonStream(stream);
|
||||
_runningTests = new ConcurrentDictionary<string, TestState>();
|
||||
}
|
||||
|
||||
public void SendTestStarted(Test test)
|
||||
{
|
||||
if (test == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(test));
|
||||
}
|
||||
|
||||
if (test.FullyQualifiedName != null)
|
||||
{
|
||||
var state = new TestState() { StartTime = DateTimeOffset.Now, };
|
||||
_runningTests.TryAdd(test.FullyQualifiedName, state);
|
||||
}
|
||||
|
||||
_stream.Send(new Message
|
||||
{
|
||||
MessageType = "TestExecution.TestStarted",
|
||||
Payload = JToken.FromObject(test),
|
||||
});
|
||||
}
|
||||
|
||||
public void SendTestResult(TestResult testResult)
|
||||
{
|
||||
if (testResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(testResult));
|
||||
}
|
||||
|
||||
if (testResult.StartTime == default(DateTimeOffset) && testResult.Test.FullyQualifiedName != null)
|
||||
{
|
||||
TestState state;
|
||||
_runningTests.TryRemove(testResult.Test.FullyQualifiedName, out state);
|
||||
|
||||
testResult.StartTime = state.StartTime;
|
||||
}
|
||||
|
||||
if (testResult.EndTime == default(DateTimeOffset))
|
||||
{
|
||||
testResult.EndTime = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
_stream.Send(new Message
|
||||
{
|
||||
MessageType = "TestExecution.TestResult",
|
||||
Payload = JToken.FromObject(testResult),
|
||||
});
|
||||
}
|
||||
|
||||
private class TestState
|
||||
{
|
||||
public DateTimeOffset StartTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue