2016-10-27 18:46:43 -07:00
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2015-12-14 17:39:29 -08:00
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.DotNet.Cli.Utils ;
using System ;
2016-12-04 11:54:29 -08:00
using System.Collections.Generic ;
2016-01-22 14:05:02 -08:00
using System.Diagnostics ;
2016-01-22 00:42:33 -08:00
using System.IO ;
2016-12-04 11:54:29 -08:00
using System.Linq ;
2016-04-04 17:51:36 -07:00
using System.Runtime.InteropServices ;
2016-12-13 14:15:35 -08:00
using System.Threading ;
2016-04-04 17:51:36 -07:00
using System.Threading.Tasks ;
2015-12-14 17:39:29 -08:00
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public class TestCommand
{
2016-04-21 13:41:22 -07:00
private string _baseDirectory ;
2016-02-18 01:09:23 -08:00
2016-12-03 14:19:54 -08:00
private List < string > _cliGeneratedEnvironmentVariables = new List < string > { "MSBuildSDKsPath" } ;
2016-12-13 14:15:35 -08:00
protected string _command ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
public Process CurrentProcess { get ; private set ; }
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
public Dictionary < string , string > Environment { get ; } = new Dictionary < string , string > ( ) ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
public event DataReceivedEventHandler ErrorDataReceived ;
2016-10-27 18:46:43 -07:00
2016-12-13 14:15:35 -08:00
public event DataReceivedEventHandler OutputDataReceived ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
public string WorkingDirectory { get ; set ; }
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
public TestCommand ( string command )
2015-12-30 17:02:59 -08:00
{
2016-12-13 14:15:35 -08:00
_command = command ;
2016-01-22 14:05:02 -08:00
2016-12-13 14:15:35 -08:00
_baseDirectory = GetBaseDirectory ( ) ;
2016-01-22 14:05:02 -08:00
}
2016-04-04 17:51:36 -07:00
public void KillTree ( )
{
if ( CurrentProcess = = null )
{
throw new InvalidOperationException ( "No process is available to be killed" ) ;
}
CurrentProcess . KillTree ( ) ;
}
2016-12-13 14:15:35 -08:00
public virtual CommandResult Execute ( string args = "" )
2016-10-27 18:46:43 -07:00
{
2016-12-13 14:15:35 -08:00
return Task . Run ( async ( ) = > await ExecuteAsync ( args ) ) . Result ;
2016-10-27 18:46:43 -07:00
}
2016-12-13 14:15:35 -08:00
public async virtual Task < CommandResult > ExecuteAsync ( string args = "" )
2016-03-15 11:50:14 -07:00
{
2016-12-13 14:15:35 -08:00
var resolvedCommand = _command ;
2016-03-15 11:50:14 -07:00
2016-12-13 14:15:35 -08:00
ResolveCommand ( ref resolvedCommand , ref args ) ;
Console . WriteLine ( $"Executing - {resolvedCommand} {args} - {WorkingDirectoryInfo()}" ) ;
return await ExecuteAsyncInternal ( resolvedCommand , args ) ;
2016-03-15 11:50:14 -07:00
}
2016-12-13 14:15:35 -08:00
public virtual CommandResult ExecuteWithCapturedOutput ( string args = "" )
2016-04-04 17:51:36 -07:00
{
2016-12-13 14:15:35 -08:00
var resolvedCommand = _command ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
ResolveCommand ( ref resolvedCommand , ref args ) ;
2016-12-04 11:54:29 -08:00
2017-03-21 14:56:12 -07:00
Console . WriteLine ( $"Executing (Captured Output) - {resolvedCommand} {args} - {WorkingDirectoryInfo()}" ) ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
return Task . Run ( async ( ) = > await ExecuteAsyncInternal ( resolvedCommand , args ) ) . Result ;
2016-04-04 17:51:36 -07:00
}
2016-12-13 14:15:35 -08:00
private async Task < CommandResult > ExecuteAsyncInternal ( string executable , string args )
2016-04-04 17:51:36 -07:00
{
2016-12-13 14:15:35 -08:00
var stdOut = new List < String > ( ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
var stdErr = new List < String > ( ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
CurrentProcess = CreateProcess ( executable , args ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
CurrentProcess . ErrorDataReceived + = ( s , e ) = >
2016-12-04 11:54:29 -08:00
{
2016-12-13 14:15:35 -08:00
stdErr . Add ( e . Data ) ;
var handler = ErrorDataReceived ;
if ( handler ! = null )
2016-12-04 11:54:29 -08:00
{
2016-12-13 14:15:35 -08:00
handler ( s , e ) ;
2016-12-04 11:54:29 -08:00
}
2016-12-13 14:15:35 -08:00
} ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
CurrentProcess . OutputDataReceived + = ( s , e ) = >
2016-12-04 11:54:29 -08:00
{
2016-12-13 14:15:35 -08:00
stdOut . Add ( e . Data ) ;
var handler = OutputDataReceived ;
if ( handler ! = null )
2016-12-04 11:54:29 -08:00
{
2016-12-13 14:15:35 -08:00
handler ( s , e ) ;
2016-12-04 11:54:29 -08:00
}
2016-12-13 14:15:35 -08:00
} ;
var completionTask = CurrentProcess . StartAndWaitForExitAsync ( ) ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
CurrentProcess . BeginOutputReadLine ( ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
CurrentProcess . BeginErrorReadLine ( ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
await completionTask ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
CurrentProcess . WaitForExit ( ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
RemoveNullTerminator ( stdOut ) ;
2016-12-04 11:54:29 -08:00
2016-12-13 14:15:35 -08:00
RemoveNullTerminator ( stdErr ) ;
2016-04-04 17:51:36 -07:00
2016-12-13 14:15:35 -08:00
return new CommandResult (
CurrentProcess . StartInfo ,
CurrentProcess . ExitCode ,
String . Join ( System . Environment . NewLine , stdOut ) ,
String . Join ( System . Environment . NewLine , stdErr ) ) ;
2016-04-04 17:51:36 -07:00
}
2016-12-04 11:54:29 -08:00
private Process CreateProcess ( string executable , string args )
2016-01-22 14:05:02 -08:00
{
var psi = new ProcessStartInfo
{
FileName = executable ,
Arguments = args ,
RedirectStandardError = true ,
2016-04-04 17:51:36 -07:00
RedirectStandardOutput = true ,
2016-04-21 13:41:22 -07:00
RedirectStandardInput = true ,
UseShellExecute = false
2016-01-22 14:05:02 -08:00
} ;
2016-12-13 14:15:35 -08:00
RemoveCliGeneratedEnvironmentVariablesFrom ( psi ) ;
2016-12-03 14:19:54 -08:00
2018-11-20 10:53:39 -08:00
psi . Environment [ "DOTNET_MULTILEVEL_LOOKUP" ] = "0" ;
2019-11-08 13:58:10 -08:00
// Set DOTNET_ROOT to ensure sub process find the same host fxr
string dotnetDirectoryPath = Path . GetDirectoryName ( RepoDirectoriesProvider . DotnetUnderTest ) ;
if ( System . Environment . Is64BitProcess )
{
psi . Environment . Add ( "DOTNET_ROOT" , dotnetDirectoryPath ) ;
}
else
{
psi . Environment . Add ( "DOTNET_ROOT(x86)" , dotnetDirectoryPath ) ;
}
2016-12-13 14:15:35 -08:00
AddEnvironmentVariablesTo ( psi ) ;
2016-02-18 01:09:23 -08:00
2016-12-13 14:15:35 -08:00
AddWorkingDirectoryTo ( psi ) ;
2016-03-28 03:18:13 -07:00
2016-01-22 14:05:02 -08:00
var process = new Process
{
2016-03-28 03:18:13 -07:00
StartInfo = psi
2016-01-22 14:05:02 -08:00
} ;
process . EnableRaisingEvents = true ;
2016-12-04 11:54:29 -08:00
2016-04-04 17:51:36 -07:00
return process ;
2015-12-30 17:02:59 -08:00
}
2016-10-27 18:46:43 -07:00
private string WorkingDirectoryInfo ( )
{
if ( WorkingDirectory = = null )
{
return "" ;
}
return $" in pwd {WorkingDirectory}" ;
2016-04-22 12:17:36 -07:00
}
2016-12-03 14:19:54 -08:00
2016-12-13 14:15:35 -08:00
private void RemoveNullTerminator ( List < string > strings )
{
var count = strings . Count ;
if ( count < 1 )
{
return ;
}
if ( strings [ count - 1 ] = = null )
{
strings . RemoveAt ( count - 1 ) ;
}
}
private string GetBaseDirectory ( )
{
#if NET451
return AppDomain . CurrentDomain . BaseDirectory ;
#else
return AppContext . BaseDirectory ;
#endif
}
private void ResolveCommand ( ref string executable , ref string args )
{
if ( executable . EndsWith ( ".dll" , StringComparison . OrdinalIgnoreCase ) )
{
var newArgs = ArgumentEscaper . EscapeSingleArg ( executable ) ;
if ( ! string . IsNullOrEmpty ( args ) )
{
newArgs + = " " + args ;
}
args = newArgs ;
2018-11-20 10:53:39 -08:00
executable = RepoDirectoriesProvider . DotnetUnderTest ;
2016-12-13 14:15:35 -08:00
}
2017-03-21 14:56:12 -07:00
else if ( executable = = "dotnet" )
{
2018-11-20 10:53:39 -08:00
executable = RepoDirectoriesProvider . DotnetUnderTest ;
2017-03-21 14:56:12 -07:00
}
else if ( ! Path . IsPathRooted ( executable ) )
2016-12-13 14:15:35 -08:00
{
executable = Env . GetCommandPath ( executable ) ? ?
Env . GetCommandPathFromRootPath ( _baseDirectory , executable ) ;
}
}
private void RemoveCliGeneratedEnvironmentVariablesFrom ( ProcessStartInfo psi )
2016-12-03 14:19:54 -08:00
{
foreach ( var name in _cliGeneratedEnvironmentVariables )
{
#if NET451
psi . EnvironmentVariables . Remove ( name ) ;
#else
psi . Environment . Remove ( name ) ;
#endif
}
}
2016-12-13 14:15:35 -08:00
private void AddEnvironmentVariablesTo ( ProcessStartInfo psi )
{
foreach ( var item in Environment )
{
#if NET451
psi . EnvironmentVariables [ item . Key ] = item . Value ;
#else
psi . Environment [ item . Key ] = item . Value ;
#endif
}
}
private void AddWorkingDirectoryTo ( ProcessStartInfo psi )
{
if ( ! string . IsNullOrWhiteSpace ( WorkingDirectory ) )
{
psi . WorkingDirectory = WorkingDirectory ;
}
}
2015-12-14 17:39:29 -08:00
}
2016-12-13 14:15:35 -08:00
}