diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Constants.cs b/src/Microsoft.TestPlatform.CoreUtilities/Constants.cs
index 1af6ef5371..e4868a75fe 100644
--- a/src/Microsoft.TestPlatform.CoreUtilities/Constants.cs
+++ b/src/Microsoft.TestPlatform.CoreUtilities/Constants.cs
@@ -28,5 +28,10 @@ public class Constants
/// error message on standard error.
///
public const int StandardErrorMaxLength = 8192; // 8 KB
+
+ ///
+ /// Environment Variable Specified by user to setup Culture.
+ ///
+ public const string DotNetUserSpecifiedCulture = "DOTNET_CLI_UI_LANGUAGE";
}
}
diff --git a/src/datacollector/DataCollectorMain.cs b/src/datacollector/DataCollectorMain.cs
index 7e61df298b..106cbda3c5 100644
--- a/src/datacollector/DataCollectorMain.cs
+++ b/src/datacollector/DataCollectorMain.cs
@@ -90,6 +90,8 @@ public void Run(string[] args)
EqtTrace.DoNotInitailize = true;
}
+ SetCultureSpecifiedByUser();
+
EqtTrace.Info("DataCollectorMain.Run: Starting data collector run with args: {0}", string.Join(",", args));
// Attach to exit of parent process
@@ -140,6 +142,22 @@ private void WaitForDebuggerIfEnabled()
}
}
+ private static void SetCultureSpecifiedByUser()
+ {
+ var userCultureSpecified = Environment.GetEnvironmentVariable(CoreUtilities.Constants.DotNetUserSpecifiedCulture);
+ if (!string.IsNullOrWhiteSpace(userCultureSpecified))
+ {
+ try
+ {
+ CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(userCultureSpecified);
+ }
+ catch (Exception)
+ {
+ EqtTrace.Info(string.Format("Invalid Culture Info: {0}", userCultureSpecified));
+ }
+ }
+ }
+
private void StartProcessing()
{
var timeout = EnvironmentHelper.GetConnectionTimeout();
diff --git a/src/testhost.x86/AppDomainEngineInvoker.cs b/src/testhost.x86/AppDomainEngineInvoker.cs
index 03a940783c..b59175a818 100644
--- a/src/testhost.x86/AppDomainEngineInvoker.cs
+++ b/src/testhost.x86/AppDomainEngineInvoker.cs
@@ -13,6 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost
using System.Reflection;
using System.Xml.Linq;
using System.Collections.Generic;
+ using System.Globalization;
///
/// Implementation for the Invoker which invokes engine in a new AppDomain
@@ -28,16 +29,31 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost
private string mergedTempConfigFile = null;
- public AppDomainEngineInvoker(string testSourcePath)
+ public AppDomainEngineInvoker(string testSourcePath, AppDomainInitializer initializer = null, string appBasePath = null)
{
TestPlatformEventSource.Instance.TestHostAppDomainCreationStart();
- this.appDomain = CreateNewAppDomain(testSourcePath);
+ this.appDomain = CreateNewAppDomain(testSourcePath, initializer, appBasePath);
+
+ // Setting appbase later, as AppDomain needs to load testhost.exe into the new Domain, to have access to AppDomainInitializer method.
+ // If we set appbase to testsource folder, then if fails to find testhost.exe resulting in FileNotFoundException for testhost.exe
+ this.UpdateAppBaseToTestSourceLocation(testSourcePath);
this.actualInvoker = CreateInvokerInAppDomain(appDomain);
TestPlatformEventSource.Instance.TestHostAppDomainCreationStop();
}
+ private void UpdateAppBaseToTestSourceLocation(string testSourcePath)
+ {
+ // Set AppBase to TestAssembly location
+ var testSourceFolder = Path.GetDirectoryName(testSourcePath);
+ if (this.appDomain != null)
+ {
+ this.appDomain.SetData("APPBASE", testSourceFolder);
+ }
+ }
+
+
///
/// Invokes the Engine with the arguments
///
@@ -71,15 +87,22 @@ public void Invoke(IDictionary argsDictionary)
}
}
- private AppDomain CreateNewAppDomain(string testSourcePath)
+ private AppDomain CreateNewAppDomain(string testSourcePath, AppDomainInitializer initializer, string appBasePath)
{
var appDomainSetup = new AppDomainSetup();
var testSourceFolder = Path.GetDirectoryName(testSourcePath);
- // Set AppBase to TestAssembly location
- appDomainSetup.ApplicationBase = testSourceFolder;
+ if (!string.IsNullOrEmpty(appBasePath))
+ {
+ appDomainSetup.ApplicationBase = appBasePath;
+ }
+
appDomainSetup.LoaderOptimization = LoaderOptimization.MultiDomainHost;
+ //Setup AppDomainInitialzier to set user defined Culture
+ appDomainSetup.AppDomainInitializer = initializer ?? SetAppDomainCulture;
+ appDomainSetup.AppDomainInitializerArguments = new string[] { };
+
// Set User Config file as app domain config
SetConfigurationFile(appDomainSetup, testSourcePath, testSourceFolder);
@@ -167,6 +190,23 @@ private static string GetConfigFile(string testSource, string testSourceFolder)
return configFile;
}
+ private static void SetAppDomainCulture(string[] args)
+ {
+ var userCultureSpecified = Environment.GetEnvironmentVariable(CoreUtilities.Constants.DotNetUserSpecifiedCulture);
+ if (!string.IsNullOrWhiteSpace(userCultureSpecified))
+ {
+ try
+ {
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(userCultureSpecified);
+ }
+ // If an exception occurs, we'll just fall back to the system default.
+ catch (Exception)
+ {
+ EqtTrace.Verbose("Invalid Culture Info '{0}:'", userCultureSpecified);
+ }
+ }
+ }
+
protected static XDocument MergeApplicationConfigFiles(XDocument userConfigDoc, XDocument testHostConfigDoc)
{
// Start with User's config file as the base
diff --git a/src/testhost.x86/Program.cs b/src/testhost.x86/Program.cs
index 48c7776429..674c4e0881 100644
--- a/src/testhost.x86/Program.cs
+++ b/src/testhost.x86/Program.cs
@@ -6,7 +6,7 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost
using System;
using System.Collections.Generic;
using System.Diagnostics;
-
+ using System.Globalization;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
@@ -52,6 +52,7 @@ public static void Main(string[] args)
public static void Run(string[] args)
{
WaitForDebuggerIfEnabled();
+ SetCultureSpecifiedByUser();
var argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary(args);
// Invoke the engine with arguments
@@ -103,5 +104,21 @@ private static void WaitForDebuggerIfEnabled()
Debugger.Break();
}
}
+
+ private static void SetCultureSpecifiedByUser()
+ {
+ var userCultureSpecified = Environment.GetEnvironmentVariable(CoreUtilities.Constants.DotNetUserSpecifiedCulture);
+ if (!string.IsNullOrWhiteSpace(userCultureSpecified))
+ {
+ try
+ {
+ CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(userCultureSpecified);
+ }
+ catch (Exception)
+ {
+ ConsoleOutput.Instance.WriteLine(string.Format("Invalid Culture Info: {0}", userCultureSpecified), OutputLevel.Information);
+ }
+ }
+ }
}
}
diff --git a/src/vstest.console/Program.cs b/src/vstest.console/Program.cs
index 2ad37db30e..4363137a53 100644
--- a/src/vstest.console/Program.cs
+++ b/src/vstest.console/Program.cs
@@ -4,6 +4,7 @@
namespace Microsoft.VisualStudio.TestPlatform.CommandLine
{
using System;
+ using System.Globalization;
using Microsoft.VisualStudio.TestPlatform.Utilities;
///
@@ -36,7 +37,25 @@ public static int Main(string[] args)
System.Diagnostics.Debugger.Break();
}
+ SetCultureSpecifiedByUser();
+
return new Executor(ConsoleOutput.Instance).Execute(args);
}
+
+ private static void SetCultureSpecifiedByUser()
+ {
+ var userCultureSpecified = Environment.GetEnvironmentVariable(CoreUtilities.Constants.DotNetUserSpecifiedCulture);
+ if(!string.IsNullOrWhiteSpace(userCultureSpecified))
+ {
+ try
+ {
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(userCultureSpecified);
+ }
+ catch(Exception)
+ {
+ ConsoleOutput.Instance.WriteLine(string.Format("Invalid Culture Info: {0}", userCultureSpecified), OutputLevel.Information);
+ }
+ }
+ }
}
}
diff --git a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs
index 6632dee675..7706e4df9d 100644
--- a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs
+++ b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs
@@ -6,6 +6,7 @@ namespace testhost.UnitTests
#if NET451
using Microsoft.VisualStudio.TestPlatform.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Microsoft.VisualStudio.TestPlatform.CoreUtilities;
using System;
using System.IO;
using System.Collections.Generic;
@@ -13,6 +14,7 @@ namespace testhost.UnitTests
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Text;
+ using System.Globalization;
[TestClass]
public class AppDomainEngineInvokerTests
@@ -47,7 +49,7 @@ public class AppDomainEngineInvokerTests
public void AppDomainEngineInvokerShouldCreateNewAppDomain()
{
var tempFile = Path.GetTempFileName();
- var appDomainInvoker = new TestableEngineInvoker(tempFile);
+ var appDomainInvoker = new TestableEngineInvoker(tempFile, AppDomain.CurrentDomain.BaseDirectory);
Assert.IsNotNull(appDomainInvoker.NewAppDomain, "New AppDomain must be created.");
Assert.IsNotNull(appDomainInvoker.ActualInvoker, "Invoker must be created.");
@@ -55,11 +57,57 @@ public void AppDomainEngineInvokerShouldCreateNewAppDomain()
"New AppDomain must be different from default one.");
}
+ [TestMethod]
+ public void AppDomainEngineInvokerShouldCreateNewAppDomainAndSetCultureAsPerUsersInput()
+ {
+ var cultureInfo = "fr-FR";
+ Environment.SetEnvironmentVariable(Constants.DotNetUserSpecifiedCulture, cultureInfo);
+ var tempFile = Path.GetTempFileName();
+ var appDomainInvoker = new TestableEngineInvoker(tempFile, AppDomain.CurrentDomain.BaseDirectory);
+ appDomainInvoker.Invoke(new Dictionary());
+
+ Assert.IsNotNull(appDomainInvoker.NewAppDomain, "New AppDomain must be created.");
+ Assert.IsNotNull(appDomainInvoker.ActualInvoker, "Invoker must be created.");
+
+ Assert.AreEqual((appDomainInvoker.ActualInvoker as MockEngineInvoker).CurrentCultureInfo, cultureInfo);
+ }
+
+ [TestMethod]
+ public void AppDomainEngineInvokerShouldCreateNewAppDomainAndSetAppBaseToSourceDirectory()
+ {
+ var cultureInfo = "fr-FR";
+ Environment.SetEnvironmentVariable(Constants.DotNetUserSpecifiedCulture, cultureInfo);
+ var tempFile = Path.GetTempFileName();
+ var appDomainInvoker = new TestableEngineInvoker(tempFile, AppDomain.CurrentDomain.BaseDirectory);
+
+ Assert.IsNotNull(appDomainInvoker.NewAppDomain, "New AppDomain must be created.");
+ Assert.IsNotNull(appDomainInvoker.ActualInvoker, "Invoker must be created.");
+
+ Assert.AreEqual(appDomainInvoker.NewAppDomain.BaseDirectory, Path.GetDirectoryName(tempFile));
+ }
+
+ [TestMethod]
+ [DataRow("garbage-culture")]
+ [DataRow(" ")]
+ [DataRow(null)]
+ public void AppDomainEngineInvokerShouldCreateNewAppDomainAndSetCultureToEnglishIfUsersInputIncorrect(string cultureInfo)
+ {
+ Environment.SetEnvironmentVariable(Constants.DotNetUserSpecifiedCulture, cultureInfo);
+ var tempFile = Path.GetTempFileName();
+ var appDomainInvoker = new TestableEngineInvoker(tempFile, AppDomain.CurrentDomain.BaseDirectory);
+ appDomainInvoker.Invoke(new Dictionary());
+
+ Assert.IsNotNull(appDomainInvoker.NewAppDomain, "New AppDomain must be created.");
+ Assert.IsNotNull(appDomainInvoker.ActualInvoker, "Invoker must be created.");
+
+ Assert.AreEqual((appDomainInvoker.ActualInvoker as MockEngineInvoker).CurrentCultureInfo, "en-US");
+ }
+
[TestMethod]
public void AppDomainEngineInvokerShouldInvokeEngineInNewDomainAndUseTestHostConfigFile()
{
var tempFile = Path.GetTempFileName();
- var appDomainInvoker = new TestableEngineInvoker(tempFile);
+ var appDomainInvoker = new TestableEngineInvoker(tempFile, AppDomain.CurrentDomain.BaseDirectory);
var newAppDomain = appDomainInvoker.NewAppDomain;
@@ -200,10 +248,26 @@ public void AppDomainEngineInvokerShouldUseDiagAndAppSettingsElementsUnMergedFro
private class TestableEngineInvoker : AppDomainEngineInvoker
{
- public TestableEngineInvoker(string testSourcePath) : base(testSourcePath)
+ public TestableEngineInvoker(string testSourcePath, string appBasePath) : base(testSourcePath, SetAppDomainCultures, appBasePath)
{
}
+ private static void SetAppDomainCultures(string[] args)
+ {
+ var userCultureSpecified = Environment.GetEnvironmentVariable(Constants.DotNetUserSpecifiedCulture);
+ if (!string.IsNullOrWhiteSpace(userCultureSpecified))
+ {
+ try
+ {
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(userCultureSpecified);
+ }
+ // If an exception occurs, we'll just fall back to the system default.
+ catch (Exception)
+ {
+ }
+ }
+ }
+
public static XDocument MergeConfigXmls(string userConfigText, string testHostConfigText)
{
return MergeApplicationConfigFiles(
@@ -219,10 +283,11 @@ public static XDocument MergeConfigXmls(string userConfigText, string testHostCo
private class MockEngineInvoker : MarshalByRefObject, IEngineInvoker
{
public string DomainFriendlyName { get; private set; }
-
+ public string CurrentCultureInfo { get; set; }
public void Invoke(IDictionary argsDictionary)
{
this.DomainFriendlyName = AppDomain.CurrentDomain.FriendlyName;
+ this.CurrentCultureInfo = CultureInfo.CurrentUICulture.Name;
}
}
}