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" : "usr/bin/dotnet-compile",
|
||||||
"bin/dotnet-compile-csc" : "usr/bin/dotnet-compile-csc",
|
"bin/dotnet-compile-csc" : "usr/bin/dotnet-compile-csc",
|
||||||
"bin/dotnet-compile-native" : "/usr/bin/dotnet-compile-native",
|
"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-publish" : "usr/bin/dotnet-publish",
|
||||||
"bin/dotnet-repl" : "usr/bin/dotnet-repl",
|
"bin/dotnet-repl" : "usr/bin/dotnet-repl",
|
||||||
"bin/dotnet-repl-csi" : "usr/bin/dotnet-repl-csi",
|
"bin/dotnet-repl-csi" : "usr/bin/dotnet-repl-csi",
|
||||||
|
|
|
@ -99,5 +99,28 @@ namespace Microsoft.DotNet.ProjectModel
|
||||||
.Build();
|
.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