@@ -231,6 +231,29 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
231231 EqtTrace . Verbose ( $ "DotnetTestHostmanager.GetTestHostProcessStartInfo: Platform environment '{ _platformEnvironment . Architecture } ' target architecture '{ _architecture } ' framework '{ _targetFramework } ' OS '{ _platformEnvironment . OperatingSystem } '") ;
232232
233233 var startInfo = new TestProcessStartInfo ( ) ;
234+ startInfo . EnvironmentVariables = environmentVariables ?? new Dictionary < string , string ? > ( StringComparer . OrdinalIgnoreCase ) ;
235+
236+ string ? dotnetRootPath = _environmentVariableHelper . GetEnvironmentVariable ( "VSTEST_DOTNET_ROOT_PATH" ) ;
237+ string ? dotnetRootArchitecture = _environmentVariableHelper . GetEnvironmentVariable ( "VSTEST_DOTNET_ROOT_ARCHITECTURE" ) ;
238+
239+ if ( ! StringUtilities . IsNullOrWhiteSpace ( dotnetRootPath ) )
240+ {
241+ if ( StringUtils . IsNullOrWhiteSpace ( dotnetRootArchitecture ) )
242+ {
243+ throw new InvalidOperationException ( "'VSTEST_DOTNET_ROOT_PATH' and 'VSTEST_DOTNET_ROOT_ARCHITECTURE' must be both always set. If you are seeing this error, this is a bug in dotnet SDK that sets those variables." ) ;
244+ }
245+
246+ EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_PATH={ dotnetRootPath } ") ;
247+ EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_ARCHITECTURE={ dotnetRootArchitecture } ") ;
248+
249+ if ( ! FeatureFlag . Instance . IsSet ( FeatureFlag . VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS ) )
250+ {
251+ // Set DOTNET_ROOT_<ARCH> for any run, so it gets propagated to testhost and its child processes, like dotnet run does it. This allows executables that start under testhost to find the path to dotnet
252+ // from which we called dotnet test. Before this change we only expected testhost.exe to be in this situation, but with xunit v3 running separate exe under testhost, the need for setting architecture
253+ // specific DOTNET_ROOT increases and makes this necessary for users to have good experience.
254+ SetDotnetRootForArchitecture ( startInfo , dotnetRootPath ! , dotnetRootArchitecture ) ;
255+ }
256+ }
234257
235258 // .NET core host manager is not a shared host. It will expect a single test source to be provided.
236259 // TODO: Throw an exception when we get 0 or more than 1 source, that explains what happened, instead of .Single throwing a generic exception?
@@ -506,29 +529,14 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
506529 // G:\nuget-package-path\microsoft.testplatform.testhost\version\**\testhost.dll
507530 // G:\tmp\netcore-test\bin\Debug\netcoreapp1.0\netcore-test.dll
508531 startInfo . Arguments = args ;
509- startInfo . EnvironmentVariables = environmentVariables ?? new Dictionary < string , string ? > ( ) ;
510532
511533 // If we're running using custom apphost we need to set DOTNET_ROOT/DOTNET_ROOT(x86)
512534 // We're setting it inside SDK to support private install scenario.
513535 // i.e. I've got only private install and no global installation, in this case apphost needs to use env var to locate runtime.
514536 if ( testHostExeFound )
515537 {
516- // This change needs to happen first on vstest side, and then on dotnet/sdk, so prefer this approach and fallback to the old one.
517- // VSTEST_DOTNET_ROOT v2
518- string ? dotnetRootPath = _environmentVariableHelper . GetEnvironmentVariable ( "VSTEST_DOTNET_ROOT_PATH" ) ;
519- if ( ! StringUtils . IsNullOrWhiteSpace ( dotnetRootPath ) )
538+ if ( ! StringUtilities . IsNullOrWhiteSpace ( dotnetRootPath ) )
520539 {
521- // This is v2 of the environment variables that we are passing, we are in new dotnet sdk. So also grab the architecture.
522- string ? dotnetRootArchitecture = _environmentVariableHelper . GetEnvironmentVariable ( "VSTEST_DOTNET_ROOT_ARCHITECTURE" ) ;
523-
524- if ( StringUtils . IsNullOrWhiteSpace ( dotnetRootArchitecture ) )
525- {
526- throw new InvalidOperationException ( "'VSTEST_DOTNET_ROOT_PATH' and 'VSTEST_DOTNET_ROOT_ARCHITECTURE' must be both always set. If you are seeing this error, this is a bug in dotnet SDK that sets those variables." ) ;
527- }
528-
529- EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_PATH={ dotnetRootPath } ") ;
530- EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_ARCHITECTURE={ dotnetRootArchitecture } ") ;
531-
532540 // The parent process is passing to us the path in which the dotnet.exe is and is passing the architecture of the dotnet.exe,
533541 // so if the child process (testhost) is the same architecture it can pick up that dotnet.exe location and run. This is to allow
534542 // local installations of dotnet/sdk to work with testhost.
@@ -560,21 +568,11 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
560568 //
561569 // We ship just testhost.exe and testhost.x86.exe if the architecture is different we won't find the testhost*.exe and
562570 // won't reach this code, but let's write this in a generic way anyway, to avoid breaking if we add more variants of testhost*.exe.
563- var environmentVariableName = $ "DOTNET_ROOT_{ _architecture . ToString ( ) . ToUpperInvariant ( ) } ";
564-
565- var existingDotnetRoot = _environmentVariableHelper . GetEnvironmentVariable ( environmentVariableName ) ;
566- if ( ! StringUtilities . IsNullOrWhiteSpace ( existingDotnetRoot ) )
567- {
568- // The variable is already set in the surrounding environment, don't set it, because we want to keep what user provided.
569- }
570- else
571+ //
572+ // If the feature flag is set, revert to previous behavior of setting DOTNET_ROOT_<ARCH> only on Windows after we found testhost.exe.
573+ if ( FeatureFlag . Instance . IsSet ( FeatureFlag . VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS ) )
571574 {
572- var architectureFromEnv = ( Architecture ) Enum . Parse ( typeof ( Architecture ) , dotnetRootArchitecture , ignoreCase : true ) ;
573- if ( architectureFromEnv == _architecture )
574- {
575- // Set the architecture specific variable to the environment of the process so it is picked up.
576- startInfo . EnvironmentVariables . Add ( environmentVariableName , dotnetRootPath ) ;
577- }
575+ SetDotnetRootForArchitecture ( startInfo , dotnetRootPath ! , dotnetRootArchitecture ! ) ;
578576 }
579577 }
580578 else
@@ -583,7 +581,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
583581 // to avoid setting DOTNET_ROOT that points to x64 but is picked up by x86 host.
584582 //
585583 // Also avoid setting it if we are already getting it from the surrounding environment.
586- var architectureFromEnv = ( Architecture ) Enum . Parse ( typeof ( Architecture ) , dotnetRootArchitecture , ignoreCase : true ) ;
584+ var architectureFromEnv = ( Architecture ) Enum . Parse ( typeof ( Architecture ) , dotnetRootArchitecture ! , ignoreCase : true ) ;
587585 if ( architectureFromEnv == _architecture )
588586 {
589587 if ( _architecture == Architecture . X86 )
@@ -605,28 +603,6 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
605603 }
606604 }
607605 }
608- else
609- {
610- // Fallback, can delete this once the change is in dotnet sdk. because they are always used together.
611- string prefix = "VSTEST_WINAPPHOST_" ;
612- string dotnetRootEnvName = $ "{ prefix } DOTNET_ROOT(x86)";
613- var dotnetRoot = _environmentVariableHelper . GetEnvironmentVariable ( dotnetRootEnvName ) ;
614- if ( dotnetRoot is null )
615- {
616- dotnetRootEnvName = $ "{ prefix } DOTNET_ROOT";
617- dotnetRoot = _environmentVariableHelper . GetEnvironmentVariable ( dotnetRootEnvName ) ;
618- }
619-
620- if ( dotnetRoot != null )
621- {
622- EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: Found '{ dotnetRootEnvName } ' in env variables, value '{ dotnetRoot } ', forwarding to '{ dotnetRootEnvName . Replace ( prefix , string . Empty ) } '") ;
623- startInfo . EnvironmentVariables . Add ( dotnetRootEnvName . Replace ( prefix , string . Empty ) , dotnetRoot ) ;
624- }
625- else
626- {
627- EqtTrace . Verbose ( $ "DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{ prefix } *' not found in env variables") ;
628- }
629- }
630606 }
631607
632608 startInfo . WorkingDirectory = sourceDirectory ;
@@ -732,6 +708,26 @@ bool IsNativeModule(string modulePath)
732708 }
733709 }
734710
711+ private void SetDotnetRootForArchitecture ( TestProcessStartInfo startInfo , string dotnetRootPath , string dotnetRootArchitecture )
712+ {
713+ var environmentVariableName = $ "DOTNET_ROOT_{ dotnetRootArchitecture . ToUpperInvariant ( ) } ";
714+
715+ var existingDotnetRoot = _environmentVariableHelper . GetEnvironmentVariable ( environmentVariableName ) ;
716+ if ( ! StringUtilities . IsNullOrWhiteSpace ( existingDotnetRoot ) )
717+ {
718+ EqtTrace . Verbose ( $ "DotnetTestHostManager.SetDotnetRootForArchitecture: The variable { environmentVariableName } is already set in the surrounding environment, don't add it to testhost start info, because we want to keep what user provided externally.") ;
719+ }
720+ else
721+ {
722+ startInfo . EnvironmentVariables ??= new Dictionary < string , string ? > ( ) ;
723+
724+ // Set the architecture specific variable to the environment of the process so it is picked up.
725+ startInfo . EnvironmentVariables . Add ( environmentVariableName , dotnetRootPath ) ;
726+
727+ EqtTrace . Verbose ( $ "DotnetTestHostManager.SetDotnetRootForArchitecture: Adding { environmentVariableName } ={ dotnetRootPath } to testhost start info.") ;
728+ }
729+ }
730+
735731 /// <inheritdoc/>
736732 public IEnumerable < string > GetTestPlatformExtensions ( IEnumerable < string > sources , IEnumerable < string > extensions )
737733 {
@@ -837,7 +833,15 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke
837833 || ( _customTestHostLauncher . IsDebug
838834 && _customTestHostLauncher is ITestHostLauncher2 ) )
839835 {
840- EqtTrace . Verbose ( "DotnetTestHostManager: Starting process '{0}' with command line '{1}'" , testHostStartInfo . FileName , testHostStartInfo . Arguments ) ;
836+ if ( EqtTrace . IsVerboseEnabled )
837+ {
838+ var dotnetEnvVars = testHostStartInfo . EnvironmentVariables ?
839+ . Where ( kvp => kvp . Key . StartsWith ( "DOTNET_" , StringComparison . OrdinalIgnoreCase ) )
840+ . Select ( kvp => $ "{ kvp . Key } ={ kvp . Value } ")
841+ . ToArray ( ) ?? Array . Empty < string > ( ) ;
842+
843+ EqtTrace . Verbose ( $ "DotnetTestHostManager: Starting process '{ 0 } ' with command line '{ 1 } ' and DOTNET environment: { string . Join ( ", " , dotnetEnvVars ) } ", testHostStartInfo . FileName , testHostStartInfo . Arguments ) ;
844+ }
841845
842846 cancellationToken . ThrowIfCancellationRequested ( ) ;
843847
0 commit comments