Skip to content

Commit

Permalink
Use Culture Specified by User in case it differs with that of OS (#1712)
Browse files Browse the repository at this point in the history
* Use Culture Specified by User in case it differs with that of OS

* setup culture on appdomain

* do not modify formatting just translation

* Initialize AppBase of new Domain after setting up CultureInfo

* Fix Tests

* add tests for setting app domain culture info
  • Loading branch information
mayankbansal018 authored Aug 3, 2018
1 parent 565dfbd commit 06101ef
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 10 deletions.
5 changes: 5 additions & 0 deletions src/Microsoft.TestPlatform.CoreUtilities/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@ public class Constants
/// error message on standard error.
/// </summary>
public const int StandardErrorMaxLength = 8192; // 8 KB

/// <summary>
/// Environment Variable Specified by user to setup Culture.
/// </summary>
public const string DotNetUserSpecifiedCulture = "DOTNET_CLI_UI_LANGUAGE";
}
}
18 changes: 18 additions & 0 deletions src/datacollector/DataCollectorMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
50 changes: 45 additions & 5 deletions src/testhost.x86/AppDomainEngineInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost
using System.Reflection;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Globalization;

/// <summary>
/// Implementation for the Invoker which invokes engine in a new AppDomain
Expand All @@ -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);
}
}


/// <summary>
/// Invokes the Engine with the arguments
/// </summary>
Expand Down Expand Up @@ -71,15 +87,22 @@ public void Invoke(IDictionary<string, string> 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);

Expand Down Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion src/testhost.x86/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}
}
}
19 changes: 19 additions & 0 deletions src/vstest.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Microsoft.VisualStudio.TestPlatform.CommandLine
{
using System;
using System.Globalization;
using Microsoft.VisualStudio.TestPlatform.Utilities;

/// <summary>
Expand Down Expand Up @@ -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);
}
}
}
}
}
73 changes: 69 additions & 4 deletions test/testhost.UnitTests/AppDomainEngineInvokerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ 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;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Text;
using System.Globalization;

[TestClass]
public class AppDomainEngineInvokerTests
Expand Down Expand Up @@ -47,19 +49,65 @@ 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.");
Assert.AreNotEqual(AppDomain.CurrentDomain.FriendlyName, appDomainInvoker.NewAppDomain.FriendlyName,
"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<string, string>());

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<string, string>());

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;

Expand Down Expand Up @@ -200,10 +248,26 @@ public void AppDomainEngineInvokerShouldUseDiagAndAppSettingsElementsUnMergedFro

private class TestableEngineInvoker : AppDomainEngineInvoker<MockEngineInvoker>
{
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(
Expand All @@ -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<string, string> argsDictionary)
{
this.DomainFriendlyName = AppDomain.CurrentDomain.FriendlyName;
this.CurrentCultureInfo = CultureInfo.CurrentUICulture.Name;
}
}
}
Expand Down

0 comments on commit 06101ef

Please sign in to comment.