2015-11-30 16:24:03 -08:00
// 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 ;
namespace Microsoft.DotNet.Tools.Test
{
2016-01-30 21:47:50 -08:00
public class TestCommand
2015-11-30 16:24:03 -08:00
{
2016-01-30 21:47:50 -08:00
public static int Run ( string [ ] args )
2015-11-30 16:24:03 -08:00
{
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 ) ;
2016-02-05 13:26:12 -08:00
var configurationOption = app . Option ( "-c|--configuration <CONFIGURATION>" , "Configuration under which to build" , CommandOptionType . SingleValue ) ;
2015-11-30 16:24:03 -08:00
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." ) ;
2016-01-26 06:39:13 -08:00
2015-11-30 16:24:03 -08:00
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 ) ;
}
2016-01-26 06:39:13 -08:00
2015-11-30 16:24:03 -08:00
var projectContexts = CreateProjectContexts ( projectPath . Value ) ;
var projectContext = projectContexts . First ( ) ;
var testRunner = projectContext . ProjectFile . TestRunner ;
2016-02-12 18:22:35 -08:00
2016-02-05 13:26:12 -08:00
var configuration = configurationOption . Value ( ) ? ? Constants . DefaultConfiguration ;
2016-01-26 06:39:13 -08:00
2015-11-30 16:24:03 -08:00
if ( portOption . HasValue ( ) )
{
int port ;
2016-01-26 06:39:13 -08:00
2015-11-30 16:24:03 -08:00
if ( ! Int32 . TryParse ( portOption . Value ( ) , out port ) )
{
throw new InvalidOperationException ( $"{portOption.Value()} is not a valid port number." ) ;
}
2016-02-05 13:26:12 -08:00
return RunDesignTime ( port , projectContext , testRunner , configuration ) ;
2015-11-30 16:24:03 -08:00
}
else
{
2016-02-05 13:26:12 -08:00
return RunConsole ( projectContext , app , testRunner , configuration ) ;
2015-11-30 16:24:03 -08:00
}
}
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 ) ;
}
2016-02-05 13:26:12 -08:00
private static int RunConsole ( ProjectContext projectContext , CommandLineApplication app , string testRunner , string configuration )
2015-11-30 16:24:03 -08:00
{
2016-02-03 10:57:25 -08:00
var commandArgs = new List < string > { projectContext . GetOutputPaths ( configuration ) . CompilationFiles . Assembly } ;
2015-11-30 16:24:03 -08:00
commandArgs . AddRange ( app . RemainingArguments ) ;
2016-02-05 18:55:15 -08:00
return Command . Create ( $"dotnet-{GetCommandName(testRunner)}" , commandArgs , projectContext . TargetFramework , configuration : configuration )
2015-11-30 16:24:03 -08:00
. ForwardStdErr ( )
. ForwardStdOut ( )
. Execute ( )
. ExitCode ;
}
2016-02-12 18:22:35 -08:00
private static int RunDesignTime (
int port ,
ProjectContext
projectContext ,
string testRunner ,
string configuration )
2015-11-30 16:24:03 -08:00
{
Console . WriteLine ( "Listening on port {0}" , port ) ;
2016-02-12 18:22:35 -08:00
HandleDesignTimeMessages ( projectContext , testRunner , port , configuration ) ;
2015-11-30 16:24:03 -08:00
2016-02-12 18:22:35 -08:00
return 0 ;
2015-11-30 16:24:03 -08:00
}
2016-02-12 18:22:35 -08:00
private static void HandleDesignTimeMessages (
ProjectContext projectContext ,
string testRunner ,
int port ,
string configuration )
2015-11-30 16:24:03 -08:00
{
2016-02-12 18:22:35 -08:00
var reportingChannelFactory = new ReportingChannelFactory ( ) ;
var adapterChannel = reportingChannelFactory . CreateChannelWithPort ( port ) ;
2015-11-30 16:24:03 -08:00
2016-02-12 18:22:35 -08:00
try
2015-11-30 16:24:03 -08:00
{
2016-02-12 18:22:35 -08:00
var assemblyUnderTest = projectContext . GetOutputPaths ( configuration ) . CompilationFiles . Assembly ;
var messages = new TestMessagesCollection ( ) ;
using ( var dotnetTest = new DotnetTest ( messages , assemblyUnderTest ) )
2015-11-30 16:24:03 -08:00
{
2016-02-26 14:31:53 -08:00
var commandFactory = new CommandFactory ( ) ;
2016-02-12 18:22:35 -08:00
var testRunnerFactory = new TestRunnerFactory ( GetCommandName ( testRunner ) , commandFactory ) ;
2015-11-30 16:24:03 -08:00
2016-02-12 18:22:35 -08:00
dotnetTest
. AddNonSpecificMessageHandlers ( messages , adapterChannel )
. AddTestDiscoveryMessageHandlers ( adapterChannel , reportingChannelFactory , testRunnerFactory )
. AddTestRunMessageHandlers ( adapterChannel , reportingChannelFactory , testRunnerFactory )
. AddTestRunnnersMessageHandlers ( adapterChannel ) ;
2015-11-30 16:24:03 -08:00
2016-02-12 18:22:35 -08:00
dotnetTest . StartListeningTo ( adapterChannel ) ;
2015-11-30 16:24:03 -08:00
2016-02-24 16:53:16 -08:00
adapterChannel . Accept ( ) ;
2016-02-12 18:22:35 -08:00
dotnetTest . StartHandlingMessages ( ) ;
2015-11-30 16:24:03 -08:00
}
}
2016-02-12 18:22:35 -08:00
catch ( Exception ex )
2015-11-30 16:24:03 -08:00
{
2016-02-12 18:22:35 -08:00
adapterChannel . SendError ( ex ) ;
2015-11-30 16:24:03 -08:00
}
}
private static string GetCommandName ( string testRunner )
{
2016-01-30 21:47:50 -08:00
return $"test-{testRunner}" ;
2015-11-30 16:24:03 -08:00
}
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 ) ;
}
}
}