From 6c1ef959cc85d1992951f8e41b64d6fa63defdc6 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Wed, 20 Apr 2016 17:53:38 -0700 Subject: [PATCH 1/9] WiP --- .../MulticoreJitProfilePathCalculator.cs | 57 ++++++++++++++ src/dotnet/Program.cs | 5 ++ src/dotnet/Telemetry.cs | 3 + .../GivenThatIWantToManageMulticoreJIT.cs | 76 +++++++++++++++++++ test/dotnet.Tests/project.json | 1 + 5 files changed, 142 insertions(+) create mode 100644 src/dotnet/MulticoreJitProfilePathCalculator.cs create mode 100644 test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs diff --git a/src/dotnet/MulticoreJitProfilePathCalculator.cs b/src/dotnet/MulticoreJitProfilePathCalculator.cs new file mode 100644 index 000000000..6f5761492 --- /dev/null +++ b/src/dotnet/MulticoreJitProfilePathCalculator.cs @@ -0,0 +1,57 @@ +// 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.IO; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.DotNet.Cli +{ + internal class MulticoreJitProfilePathCalculator + { + private string _multicoreJitProfilePath; + + public string MulticoreJitProfilePath + { + get + { + if (_multicoreJitProfilePath == null) + { + CalculateProfileRootPath(); + } + + return _multicoreJitProfilePath; + } + } + + private void CalculateProfileRootPath() + { + var profileRoot = GetRuntimeDataRootPath(); + + var version = Product.Version; + + var rid = PlatformServices.Default.Runtime.GetRuntimeIdentifier(); + + _multicoreJitProfilePath = Path.Combine(profileRoot, "sdk", version, rid, "optimizationdata"); + } + + private string GetRuntimeDataRootPath() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? GetWindowsRuntimeDataRoot() + : GetNonWindowsRuntimeDataRoot(); + } + + private static string GetWindowsRuntimeDataRoot() + { + return $@"{(Environment.GetEnvironmentVariable("LocalAppData"))}\Microsoft\dotnet\"; + } + + private static string GetNonWindowsRuntimeDataRoot() + { + return $"{(Environment.GetEnvironmentVariable("HOME"))}/.dotnet/"; + } + } +} diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 4f51f9180..55a16a3d8 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Loader; using System.Text; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.InternalAbstractions; @@ -42,6 +43,10 @@ namespace Microsoft.DotNet.Cli { DebugHelper.HandleDebugSwitch(ref args); + AssemblyLoadContext.Default.SetProfileOptimizationRoot( + new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath); + AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); + if (Env.GetEnvironmentVariableAsBool("DOTNET_CLI_CAPTURE_TIMING", false)) { PerfTrace.Enabled = true; diff --git a/src/dotnet/Telemetry.cs b/src/dotnet/Telemetry.cs index 447322a51..dc3a457f3 100644 --- a/src/dotnet/Telemetry.cs +++ b/src/dotnet/Telemetry.cs @@ -1,3 +1,6 @@ +// 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; diff --git a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs new file mode 100644 index 000000000..7b181f944 --- /dev/null +++ b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs @@ -0,0 +1,76 @@ +// 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 Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; +using FluentAssertions; + +namespace Microsoft.DotNet.Tests +{ + public class GivenThatIWantToManageMulticoreJIT : TestBase + { + ITestOutputHelper _output; + private const string OptimizationProfileFileName = "dotnet."; + private readonly string _optimizationProfileFilePath; + + public GivenThatIWantToManageMulticoreJIT(ITestOutputHelper output) + { + _output = output; + _optimizationProfileFilePath = GetOptimizationProfileFilePath(); + } + + [Fact] + public void When_invoked_it_writes_optimization_data_to_the_profile_root() + { + var testStartTime = DateTime.UtcNow; + + new TestCommand("dotnet") + .Execute("--version"); + + File.Exists(_optimizationProfileFilePath) + .Should().BeTrue("Because dotnet CLI creates it after each run"); + + File.GetLastWriteTimeUtc(_optimizationProfileFilePath) + .Should().BeOnOrAfter(testStartTime, "Because dotnet CLI was executed after that time."); + } + + private static string GetOptimizationProfileFilePath() + { + Console.WriteLine(GetOptimizationRootPath(GetDotnetVersion())); + return Path.Combine(GetOptimizationRootPath(GetDotnetVersion()), + OptimizationProfileFileName); + } + + private static string GetOptimizationRootPath(string version) + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? GetWindowsProfileRoot(version) + : GetNonWindowsProfileRoot(version); + } + + private static string GetWindowsProfileRoot(string version) + { + return $@"{(Environment.GetEnvironmentVariable("LocalAppData"))}\Microsoft\dotnet\sdk\{version}\optimizationdata"; + } + + private static string GetNonWindowsProfileRoot(string version) + { + return $"{(Environment.GetEnvironmentVariable("HOME"))}/.dotnet/sdk/{version}/optimizationdata"; + } + + private static string GetDotnetVersion() + { + return Command.Create("dotnet", new[] { "--version" }) + .CaptureStdOut() + .Execute() + .StdOut + .Trim(); + } + } +} diff --git a/test/dotnet.Tests/project.json b/test/dotnet.Tests/project.json index 910296432..899d5e4d2 100644 --- a/test/dotnet.Tests/project.json +++ b/test/dotnet.Tests/project.json @@ -16,6 +16,7 @@ "target": "project", "type": "build" }, + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20100", "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-rc2-173361-36" }, From 94e620088ebe2d186927bfaa5b6dd1907b3cf6d8 Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Fri, 22 Apr 2016 12:17:36 -0700 Subject: [PATCH 2/9] Test Infrastructure Updates Creates a TestDirectory abstraction under TestInstance to manage creation of test-specific working directories Enables TestAssetManager to create TestDirectory instances Enables fluent addition of Environment Variables to TestCommand Adds PathUtility support for ensuring a directory exists --- src/Microsoft.DotNet.Cli.Utils/PathUtility.cs | 10 +- ...tAssetsManager.cs => TestAssetsManager.cs} | 7 + .../TestDirectory.cs | 40 ++++++ .../TestInstance.cs | 121 ++++++++++++++++++ .../Commands/TestCommand.cs | 7 + 5 files changed, 183 insertions(+), 2 deletions(-) rename src/Microsoft.DotNet.TestFramework/{Microsoft.DotNet.TestFramework.TestAssetsManager.cs => TestAssetsManager.cs} (92%) create mode 100644 src/Microsoft.DotNet.TestFramework/TestDirectory.cs create mode 100644 src/Microsoft.DotNet.TestFramework/TestInstance.cs diff --git a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs index 483c7c38b..c2e9c36bb 100644 --- a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs +++ b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs @@ -59,9 +59,15 @@ namespace Microsoft.DotNet.Tools.Common public static void EnsureParentDirectory(string filePath) { string directory = Path.GetDirectoryName(filePath); - if (!Directory.Exists(directory)) + + EnsureDirectory(directory); + } + + public static void EnsureDirectory(string directoryPath) + { + if (!Directory.Exists(directoryPath)) { - Directory.CreateDirectory(directory); + Directory.CreateDirectory(directoryPath); } } diff --git a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs similarity index 92% rename from src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs rename to src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs index fcf9b5ad6..e31e00b01 100644 --- a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs +++ b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs @@ -102,5 +102,12 @@ namespace Microsoft.DotNet.TestFramework var testInstance = new TestInstance(testProjectDir, testDestination); return testInstance; } + + public TestDirectory CreateTestDirectory([CallerMemberName] string callingMethod = "", string identifier = "") + { + string testDestination = Path.Combine(AppContext.BaseDirectory, callingMethod + identifier); + + return new TestDirectory(testDestination); + } } } diff --git a/src/Microsoft.DotNet.TestFramework/TestDirectory.cs b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs new file mode 100644 index 000000000..573120da1 --- /dev/null +++ b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs @@ -0,0 +1,40 @@ +// 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.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.DotNet.TestFramework +{ + public class TestDirectory + { + private readonly string _path; + + internal TestDirectory(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("path"); + } + + _path = path; + + EnsureExistsAndEmpty(_path); + } + + public string Path { get { return _path; } } + + private void EnsureExistsAndEmpty(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + + Directory.CreateDirectory(path); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.TestFramework/TestInstance.cs b/src/Microsoft.DotNet.TestFramework/TestInstance.cs new file mode 100644 index 000000000..d6489bf34 --- /dev/null +++ b/src/Microsoft.DotNet.TestFramework/TestInstance.cs @@ -0,0 +1,121 @@ +// 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.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.DotNet.TestFramework +{ + public class TestInstance: TestDirectory + { + private string _testAssetRoot; + + internal TestInstance(string testAssetRoot, string testDestination) : base(testDestination) + { + if (string.IsNullOrEmpty(testAssetRoot)) + { + throw new ArgumentException("testAssetRoot"); + } + + _testAssetRoot = testAssetRoot; + + CopySource(); + } + + private void CopySource() + { + var sourceDirs = Directory.GetDirectories(_testAssetRoot, "*", SearchOption.AllDirectories) + .Where(dir => + { + dir = dir.ToLower(); + return !dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}bin") + && !dir.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + && !dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}obj") + && !dir.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); + }); + + foreach (string sourceDir in sourceDirs) + { + Directory.CreateDirectory(sourceDir.Replace(_testAssetRoot, Path)); + } + + var sourceFiles = Directory.GetFiles(_testAssetRoot, "*.*", SearchOption.AllDirectories) + .Where(file => + { + file = file.ToLower(); + return !file.EndsWith("project.lock.json") + && !file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + && !file.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); + }); + + foreach (string srcFile in sourceFiles) + { + string destFile = srcFile.Replace(_testAssetRoot, Path); + File.Copy(srcFile, destFile, true); + FixTimeStamp(srcFile, destFile); + } + } + + public TestInstance WithLockFiles() + { + foreach (string lockFile in Directory.GetFiles(_testAssetRoot, "project.lock.json", SearchOption.AllDirectories)) + { + string destinationLockFile = lockFile.Replace(_testAssetRoot, Path); + File.Copy(lockFile, destinationLockFile, true); + FixTimeStamp(lockFile, destinationLockFile); + } + + return this; + } + + public TestInstance WithBuildArtifacts() + { + var binDirs = Directory.GetDirectories(_testAssetRoot, "*", SearchOption.AllDirectories) + .Where(dir => + { + dir = dir.ToLower(); + return dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}bin") + || dir.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + || dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}obj") + || dir.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); + }); + + foreach (string dirPath in binDirs) + { + Directory.CreateDirectory(dirPath.Replace(_testAssetRoot, Path)); + } + + var binFiles = Directory.GetFiles(_testAssetRoot, "*.*", SearchOption.AllDirectories) + .Where(file => + { + file = file.ToLower(); + return file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + || file.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); + }); + + foreach (string binFile in binFiles) + { + string destFile = binFile.Replace(_testAssetRoot, Path); + File.Copy(binFile, destFile, true); + FixTimeStamp(binFile, destFile); + } + + return this; + } + + public string TestRoot => Path; + + private static void FixTimeStamp(string originalFile, string newFile) + { + // workaround for https://github.com/dotnet/corefx/issues/6083 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var originalTime = File.GetLastWriteTime(originalFile); + File.SetLastWriteTime(newFile, originalTime); + } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs index df396972e..c6944e8bc 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs @@ -193,5 +193,12 @@ namespace Microsoft.DotNet.Tools.Test.Utilities process.Start(); return process; } + + public TestCommand WithEnvironmentVariable(string name, string value) + { + Environment.Add(name, value); + + return this; + } } } From 304434433be5b498c9f1b6b7bea7c9afe0fa8ed0 Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Fri, 22 Apr 2016 12:23:26 -0700 Subject: [PATCH 3/9] Feature Complete - Add remaining test scenarios - Refactor implementation - Add gitignore entry for optimization profiles --- .gitignore | 3 + src/dotnet/MulticoreJitActivator.cs | 46 ++++++++ src/dotnet/Program.cs | 8 +- .../GivenThatIWantToManageMulticoreJIT.cs | 107 ++++++++++++++---- test/dotnet.Tests/TestCommandExtensions.cs | 31 +++++ 5 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 src/dotnet/MulticoreJitActivator.cs create mode 100644 test/dotnet.Tests/TestCommandExtensions.cs diff --git a/.gitignore b/.gitignore index 7e56f9616..5ccf3a317 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ # Test results *-testResults.xml +# Multicore JIT Optimization profiles +**/optimizationdata/dotnet + # NuGet keeps dropping Library/ diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs new file mode 100644 index 000000000..0663b697b --- /dev/null +++ b/src/dotnet/MulticoreJitActivator.cs @@ -0,0 +1,46 @@ +// 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.IO; +using System.Runtime.Loader; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Common; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli +{ + public class MulticoreJitActivator + { + public bool TryActivateMulticoreJit() + { + var disableMulticoreJit = IsMulticoreJitDisabled(); + + if (disableMulticoreJit) + { + return false; + } + + StartCliProfileOptimization(); + + return true; + } + + private bool IsMulticoreJitDisabled() + { + return Environment.GetEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT") == "1"; + } + + private void StartCliProfileOptimization() + { + var profileOptimizationRootPath = new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath; + + PathUtility.EnsureDirectory(profileOptimizationRootPath); + + AssemblyLoadContext.Default.SetProfileOptimizationRoot(profileOptimizationRootPath); + + AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); + } + } +} \ No newline at end of file diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 55a16a3d8..fd8fcbb31 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -42,11 +42,9 @@ namespace Microsoft.DotNet.Cli public static int Main(string[] args) { DebugHelper.HandleDebugSwitch(ref args); - - AssemblyLoadContext.Default.SetProfileOptimizationRoot( - new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath); - AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); - + + new MulticoreJitActivator().TryActivateMulticoreJit(); + if (Env.GetEnvironmentVariableAsBool("DOTNET_CLI_CAPTURE_TIMING", false)) { PerfTrace.Enabled = true; diff --git a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs index 7b181f944..97d80a577 100644 --- a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs +++ b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information.  using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.TestFramework; using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.Extensions.PlatformAbstractions; using System; @@ -16,52 +17,101 @@ namespace Microsoft.DotNet.Tests public class GivenThatIWantToManageMulticoreJIT : TestBase { ITestOutputHelper _output; - private const string OptimizationProfileFileName = "dotnet."; - private readonly string _optimizationProfileFilePath; + private const string OptimizationProfileFileName = "dotnet"; public GivenThatIWantToManageMulticoreJIT(ITestOutputHelper output) { _output = output; - _optimizationProfileFilePath = GetOptimizationProfileFilePath(); } [Fact] - public void When_invoked_it_writes_optimization_data_to_the_profile_root() + public void When_invoked_then_dotnet_writes_optimization_data_to_the_profile_root() { - var testStartTime = DateTime.UtcNow; - + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + new TestCommand("dotnet") - .Execute("--version"); + .WithUserProfileRoot(testDirectory.Path) + .ExecuteWithCapturedOutput("--help"); + - File.Exists(_optimizationProfileFilePath) + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + File.Exists(optimizationProfileFilePath) .Should().BeTrue("Because dotnet CLI creates it after each run"); - File.GetLastWriteTimeUtc(_optimizationProfileFilePath) - .Should().BeOnOrAfter(testStartTime, "Because dotnet CLI was executed after that time."); + File.GetLastWriteTimeUtc(optimizationProfileFilePath) + .Should().BeOnOrAfter(testStartTime, "Because dotnet CLI was executed after that time"); } - private static string GetOptimizationProfileFilePath() + [Fact] + public void When_invoked_with_MulticoreJit_disabled_then_dotnet_does_not_writes_optimization_data_to_the_profile_root() { - Console.WriteLine(GetOptimizationRootPath(GetDotnetVersion())); - return Path.Combine(GetOptimizationRootPath(GetDotnetVersion()), + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + + new TestCommand("dotnet") + .WithUserProfileRoot(testDirectory.Path) + .WithEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT", "1") + .ExecuteWithCapturedOutput("--help"); + + + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + File.Exists(optimizationProfileFilePath) + .Should().BeFalse("Because multicore JIT is disabled"); + } + + [Fact] + public void When_the_profile_root_is_undefined_then_dotnet_does_not_crash() + { + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + new TestCommand("dotnet") + .WithUserProfileRoot("") + .ExecuteWithCapturedOutput("--help") + .Should().Pass(); + } + + [Fact] + public void When_cli_repo_builds_then_dotnet_writes_optimization_data_to_the_default_profile_root() + { + var optimizationProfileFilePath = GetOptimizationProfileFilePath(); + + File.Exists(optimizationProfileFilePath) + .Should().BeTrue("Because the dotnet building dotnet writes to the default root"); + } + + private static string GetOptimizationProfileFilePath(string userHomePath = null) + { + return Path.Combine( + GetUserProfileRoot(userHomePath), + GetOptimizationRootPath(GetDotnetVersion()), OptimizationProfileFileName); } + + private static string GetUserProfileRoot(string overrideUserProfileRoot = null) + { + if (overrideUserProfileRoot != null) + { + return overrideUserProfileRoot; + } + + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Environment.GetEnvironmentVariable("LocalAppData") + : Environment.GetEnvironmentVariable("HOME"); + } private static string GetOptimizationRootPath(string version) { + var rid = PlatformServices.Default.Runtime.GetRuntimeIdentifier(); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? GetWindowsProfileRoot(version) - : GetNonWindowsProfileRoot(version); - } - - private static string GetWindowsProfileRoot(string version) - { - return $@"{(Environment.GetEnvironmentVariable("LocalAppData"))}\Microsoft\dotnet\sdk\{version}\optimizationdata"; - } - - private static string GetNonWindowsProfileRoot(string version) - { - return $"{(Environment.GetEnvironmentVariable("HOME"))}/.dotnet/sdk/{version}/optimizationdata"; + ? $@"Microsoft\dotnet\sdk\{version}{rid}\optimizationdata" + : $@".dotnet/sdk/{version}/{rid}/optimizationdata"; } private static string GetDotnetVersion() @@ -72,5 +122,12 @@ namespace Microsoft.DotNet.Tests .StdOut .Trim(); } + + private static DateTime GetTruncatedDateTime() + { + var dt = DateTime.UtcNow; + + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0, dt.Kind); + } } } diff --git a/test/dotnet.Tests/TestCommandExtensions.cs b/test/dotnet.Tests/TestCommandExtensions.cs new file mode 100644 index 000000000..5cd99dab1 --- /dev/null +++ b/test/dotnet.Tests/TestCommandExtensions.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; +using FluentAssertions; + +namespace Microsoft.DotNet.Tests +{ + public static class TestCommandExtensions + { + public static TestCommand WithUserProfileRoot(this TestCommand testCommand, string path) + { + var userProfileEnvironmentVariableName = GetUserProfileEnvironmentVariableName(); + return testCommand.WithEnvironmentVariable(userProfileEnvironmentVariableName, path); + } + + private static string GetUserProfileEnvironmentVariableName() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "LocalAppData" + : "HOME"; + } + } +} From 91bfc022a210f1fc7d426e84a8aee746b25e55af Mon Sep 17 00:00:00 2001 From: PiotrP Date: Mon, 25 Apr 2016 11:22:32 -0700 Subject: [PATCH 4/9] PR Feedback --- .../TestDirectory.cs | 14 ++++++-------- src/dotnet/MulticoreJitActivator.cs | 2 +- src/dotnet/MulticoreJitProfilePathCalculator.cs | 4 ++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.DotNet.TestFramework/TestDirectory.cs b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs index 573120da1..e599b79e7 100644 --- a/src/Microsoft.DotNet.TestFramework/TestDirectory.cs +++ b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs @@ -11,23 +11,21 @@ namespace Microsoft.DotNet.TestFramework { public class TestDirectory { - private readonly string _path; - internal TestDirectory(string path) { if (string.IsNullOrEmpty(path)) { - throw new ArgumentException("path"); + throw new ArgumentException(nameof(path)); } - _path = path; + Path = path; - EnsureExistsAndEmpty(_path); + EnsureExistsAndEmpty(Path); } - public string Path { get { return _path; } } + public string Path { get; private set; } - private void EnsureExistsAndEmpty(string path) + private static void EnsureExistsAndEmpty(string path) { if (Directory.Exists(path)) { @@ -37,4 +35,4 @@ namespace Microsoft.DotNet.TestFramework Directory.CreateDirectory(path); } } -} \ No newline at end of file +} diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs index 0663b697b..d09974ed1 100644 --- a/src/dotnet/MulticoreJitActivator.cs +++ b/src/dotnet/MulticoreJitActivator.cs @@ -43,4 +43,4 @@ namespace Microsoft.DotNet.Cli AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); } } -} \ No newline at end of file +} diff --git a/src/dotnet/MulticoreJitProfilePathCalculator.cs b/src/dotnet/MulticoreJitProfilePathCalculator.cs index 6f5761492..0f2afc3bf 100644 --- a/src/dotnet/MulticoreJitProfilePathCalculator.cs +++ b/src/dotnet/MulticoreJitProfilePathCalculator.cs @@ -28,7 +28,7 @@ namespace Microsoft.DotNet.Cli private void CalculateProfileRootPath() { - var profileRoot = GetRuntimeDataRootPath(); + var profileRoot = GetRuntimeDataRootPathString(); var version = Product.Version; @@ -37,7 +37,7 @@ namespace Microsoft.DotNet.Cli _multicoreJitProfilePath = Path.Combine(profileRoot, "sdk", version, rid, "optimizationdata"); } - private string GetRuntimeDataRootPath() + private string GetRuntimeDataRootPathString() { return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? GetWindowsRuntimeDataRoot() From 44f66421169e8ebba0f8c0515212a7ef893e737d Mon Sep 17 00:00:00 2001 From: PiotrP Date: Mon, 25 Apr 2016 11:29:29 -0700 Subject: [PATCH 5/9] Rebase Conflicts --- ...osoft.DotNet.TestFramework.TestInstance.cs | 133 ------------------ .../TestAssetsManager.cs | 2 +- .../TestDirectory.cs | 4 +- .../TestInstance.cs | 12 +- src/dotnet/Program.cs | 4 +- .../ProjectNameArgumentTests.cs | 1 + .../GivenThatIWantToManageMulticoreJIT.cs | 2 +- 7 files changed, 17 insertions(+), 141 deletions(-) delete mode 100644 src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs diff --git a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs b/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs deleted file mode 100644 index f9903bdb5..000000000 --- a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestInstance.cs +++ /dev/null @@ -1,133 +0,0 @@ -// 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.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Microsoft.DotNet.TestFramework -{ - public class TestInstance - { - // made tolower because the rest of the class works with normalized tolower strings - private static readonly IEnumerable BuildArtifactBlackList = new List() {".IncrementalCache", ".SDKVersion"}.Select(s => s.ToLower()).ToArray(); - - private string _testDestination; - private string _testAssetRoot; - - internal TestInstance(string testAssetRoot, string testDestination) - { - Console.WriteLine($"Copying {testAssetRoot} to {testDestination}"); - if (string.IsNullOrEmpty(testAssetRoot)) - { - throw new ArgumentException("testScenario"); - } - - if (string.IsNullOrEmpty(testDestination)) - { - throw new ArgumentException("testDestination"); - } - - _testAssetRoot = testAssetRoot; - _testDestination = testDestination; - - if (Directory.Exists(testDestination)) - { - Directory.Delete(testDestination, true); - } - - Directory.CreateDirectory(testDestination); - CopySource(); - } - - private void CopySource() - { - var sourceDirs = Directory.GetDirectories(_testAssetRoot, "*", SearchOption.AllDirectories) - .Where(dir => - { - dir = dir.ToLower(); - return !dir.EndsWith($"{Path.DirectorySeparatorChar}bin") - && !dir.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - && !dir.EndsWith($"{Path.DirectorySeparatorChar}obj") - && !dir.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); - }); - - foreach (string sourceDir in sourceDirs) - { - Directory.CreateDirectory(sourceDir.Replace(_testAssetRoot, _testDestination)); - } - - var sourceFiles = Directory.GetFiles(_testAssetRoot, "*.*", SearchOption.AllDirectories) - .Where(file => - { - file = file.ToLower(); - return !file.EndsWith("project.lock.json") - && !file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - && !file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); - }); - - foreach (string srcFile in sourceFiles) - { - string destFile = srcFile.Replace(_testAssetRoot, _testDestination); - File.Copy(srcFile, destFile, true); - } - } - - public TestInstance WithLockFiles() - { - foreach (string lockFile in Directory.GetFiles(_testAssetRoot, "project.lock.json", SearchOption.AllDirectories)) - { - string destinationLockFile = lockFile.Replace(_testAssetRoot, _testDestination); - File.Copy(lockFile, destinationLockFile, true); - } - - return this; - } - - public TestInstance WithBuildArtifacts() - { - var binDirs = Directory.GetDirectories(_testAssetRoot, "*", SearchOption.AllDirectories) - .Where(dir => - { - dir = dir.ToLower(); - return dir.EndsWith($"{Path.DirectorySeparatorChar}bin") - || dir.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - || dir.EndsWith($"{Path.DirectorySeparatorChar}obj") - || dir.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); - }); - - foreach (string dirPath in binDirs) - { - Directory.CreateDirectory(dirPath.Replace(_testAssetRoot, _testDestination)); - } - - var binFiles = Directory.GetFiles(_testAssetRoot, "*.*", SearchOption.AllDirectories) - .Where(file => - { - file = file.ToLower(); - - var isArtifact = file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - || file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); - - var isBlackListed = BuildArtifactBlackList.Any(b => file.Contains(b)); - - return isArtifact && !isBlackListed; - }); - - foreach (string binFile in binFiles) - { - string destFile = binFile.Replace(_testAssetRoot, _testDestination); - File.Copy(binFile, destFile, true); - } - - return this; - } - - public string TestRoot - { - get { return _testDestination; } - } - } -} diff --git a/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs index e31e00b01..61872b1b2 100644 --- a/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs +++ b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs @@ -102,7 +102,7 @@ namespace Microsoft.DotNet.TestFramework var testInstance = new TestInstance(testProjectDir, testDestination); return testInstance; } - + public TestDirectory CreateTestDirectory([CallerMemberName] string callingMethod = "", string identifier = "") { string testDestination = Path.Combine(AppContext.BaseDirectory, callingMethod + identifier); diff --git a/src/Microsoft.DotNet.TestFramework/TestDirectory.cs b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs index e599b79e7..d76387d8a 100644 --- a/src/Microsoft.DotNet.TestFramework/TestDirectory.cs +++ b/src/Microsoft.DotNet.TestFramework/TestDirectory.cs @@ -22,9 +22,9 @@ namespace Microsoft.DotNet.TestFramework EnsureExistsAndEmpty(Path); } - + public string Path { get; private set; } - + private static void EnsureExistsAndEmpty(string path) { if (Directory.Exists(path)) diff --git a/src/Microsoft.DotNet.TestFramework/TestInstance.cs b/src/Microsoft.DotNet.TestFramework/TestInstance.cs index d6489bf34..a5fd9bee7 100644 --- a/src/Microsoft.DotNet.TestFramework/TestInstance.cs +++ b/src/Microsoft.DotNet.TestFramework/TestInstance.cs @@ -11,6 +11,9 @@ namespace Microsoft.DotNet.TestFramework { public class TestInstance: TestDirectory { + // made tolower because the rest of the class works with normalized tolower strings + private static readonly IEnumerable BuildArtifactBlackList = new List() {".IncrementalCache", ".SDKVersion"}.Select(s => s.ToLower()).ToArray(); + private string _testAssetRoot; internal TestInstance(string testAssetRoot, string testDestination) : base(testDestination) @@ -21,7 +24,7 @@ namespace Microsoft.DotNet.TestFramework } _testAssetRoot = testAssetRoot; - + CopySource(); } @@ -92,8 +95,13 @@ namespace Microsoft.DotNet.TestFramework .Where(file => { file = file.ToLower(); - return file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + + var isArtifact = file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") || file.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); + + var isBlackListed = BuildArtifactBlackList.Any(b => file.Contains(b)); + + return isArtifact && !isBlackListed; }); foreach (string binFile in binFiles) diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index fd8fcbb31..4a6641ba2 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -42,9 +42,9 @@ namespace Microsoft.DotNet.Cli public static int Main(string[] args) { DebugHelper.HandleDebugSwitch(ref args); - + new MulticoreJitActivator().TryActivateMulticoreJit(); - + if (Env.GetEnvironmentVariableAsBool("DOTNET_CLI_CAPTURE_TIMING", false)) { PerfTrace.Enabled = true; diff --git a/test/dotnet-build.Tests/ProjectNameArgumentTests.cs b/test/dotnet-build.Tests/ProjectNameArgumentTests.cs index 611540b1c..c46ee18f3 100644 --- a/test/dotnet-build.Tests/ProjectNameArgumentTests.cs +++ b/test/dotnet-build.Tests/ProjectNameArgumentTests.cs @@ -96,6 +96,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests { Test(new string[] { }, new[] { "L21" }, workingDirectory: Path.Combine("src", "L21")); } + [Fact] public void TestFailsIfNoProjectJsonInCurrentDirectoryWithNoArguments() { diff --git a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs index 97d80a577..d3dcc3e3b 100644 --- a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs +++ b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs @@ -110,7 +110,7 @@ namespace Microsoft.DotNet.Tests var rid = PlatformServices.Default.Runtime.GetRuntimeIdentifier(); return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? $@"Microsoft\dotnet\sdk\{version}{rid}\optimizationdata" + ? $@"Microsoft\dotnet\sdk\{version}\{rid}\optimizationdata" : $@".dotnet/sdk/{version}/{rid}/optimizationdata"; } From 84f10f58e9cc1c61f6d9ebcf58210fbec6a26fec Mon Sep 17 00:00:00 2001 From: PiotrP Date: Mon, 25 Apr 2016 18:56:06 -0700 Subject: [PATCH 6/9] Remove FixTimeStamp as https://github.com/dotnet/corefx/issues/6083 seems to be fixed. --- src/Microsoft.DotNet.TestFramework/TestInstance.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Microsoft.DotNet.TestFramework/TestInstance.cs b/src/Microsoft.DotNet.TestFramework/TestInstance.cs index a5fd9bee7..c3cfdfc86 100644 --- a/src/Microsoft.DotNet.TestFramework/TestInstance.cs +++ b/src/Microsoft.DotNet.TestFramework/TestInstance.cs @@ -58,7 +58,6 @@ namespace Microsoft.DotNet.TestFramework { string destFile = srcFile.Replace(_testAssetRoot, Path); File.Copy(srcFile, destFile, true); - FixTimeStamp(srcFile, destFile); } } @@ -68,7 +67,6 @@ namespace Microsoft.DotNet.TestFramework { string destinationLockFile = lockFile.Replace(_testAssetRoot, Path); File.Copy(lockFile, destinationLockFile, true); - FixTimeStamp(lockFile, destinationLockFile); } return this; @@ -108,22 +106,11 @@ namespace Microsoft.DotNet.TestFramework { string destFile = binFile.Replace(_testAssetRoot, Path); File.Copy(binFile, destFile, true); - FixTimeStamp(binFile, destFile); } return this; } public string TestRoot => Path; - - private static void FixTimeStamp(string originalFile, string newFile) - { - // workaround for https://github.com/dotnet/corefx/issues/6083 - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var originalTime = File.GetLastWriteTime(originalFile); - File.SetLastWriteTime(newFile, originalTime); - } - } } } From 3c2459afa21b3bf7ee6ca2e4e7f4bc6047aec37a Mon Sep 17 00:00:00 2001 From: PiotrP Date: Mon, 25 Apr 2016 19:24:06 -0700 Subject: [PATCH 7/9] net451 build issue --- .../TestAssetsManager.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs index 61872b1b2..4e132529d 100644 --- a/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs +++ b/src/Microsoft.DotNet.TestFramework/TestAssetsManager.cs @@ -93,21 +93,26 @@ namespace Microsoft.DotNet.TestFramework throw new Exception($"Cannot find '{testProjectName}' at '{AssetsRoot}'"); } -#if NET451 - string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; -#else - string baseDirectory = AppContext.BaseDirectory; -#endif - string testDestination = Path.Combine(baseDirectory, callingMethod + identifier, testProjectName); + var testDestination = GetTestDestinationDirectoryPath(testProjectName, callingMethod, identifier); var testInstance = new TestInstance(testProjectDir, testDestination); return testInstance; } public TestDirectory CreateTestDirectory([CallerMemberName] string callingMethod = "", string identifier = "") { - string testDestination = Path.Combine(AppContext.BaseDirectory, callingMethod + identifier); - + var testDestination = GetTestDestinationDirectoryPath(string.Empty, callingMethod, identifier); + return new TestDirectory(testDestination); } + + private string GetTestDestinationDirectoryPath(string testProjectName, string callingMethod, string identifier) + { +#if NET451 + string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; +#else + string baseDirectory = AppContext.BaseDirectory; +#endif + return Path.Combine(baseDirectory, callingMethod + identifier, testProjectName); + } } } From 9f0dab1a087dddcb00df3bf157eaa5447dd02f4f Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Tue, 3 May 2016 11:38:55 -0700 Subject: [PATCH 8/9] PR Feedback --- src/dotnet/MulticoreJitActivator.cs | 2 +- test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs | 2 -- test/dotnet.Tests/project.json | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs index d09974ed1..aeda45db5 100644 --- a/src/dotnet/MulticoreJitActivator.cs +++ b/src/dotnet/MulticoreJitActivator.cs @@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Cli private bool IsMulticoreJitDisabled() { - return Environment.GetEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT") == "1"; + return Env.GetEnvironmentVariableAsBool("DOTNET_DISABLE_MULTICOREJIT"); } private void StartCliProfileOptimization() diff --git a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs index d3dcc3e3b..a9d398bd3 100644 --- a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs +++ b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs @@ -33,7 +33,6 @@ namespace Microsoft.DotNet.Tests new TestCommand("dotnet") .WithUserProfileRoot(testDirectory.Path) .ExecuteWithCapturedOutput("--help"); - var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); @@ -54,7 +53,6 @@ namespace Microsoft.DotNet.Tests .WithUserProfileRoot(testDirectory.Path) .WithEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT", "1") .ExecuteWithCapturedOutput("--help"); - var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); diff --git a/test/dotnet.Tests/project.json b/test/dotnet.Tests/project.json index 899d5e4d2..f5205ee3d 100644 --- a/test/dotnet.Tests/project.json +++ b/test/dotnet.Tests/project.json @@ -16,7 +16,7 @@ "target": "project", "type": "build" }, - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20100", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20581", "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-rc2-173361-36" }, From 333fbbd16770e34276c0f54cec2521d1060fb7d7 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Tue, 3 May 2016 12:47:10 -0700 Subject: [PATCH 9/9] When profile directory does not exist Then skip MCJ --- src/dotnet/MulticoreJitActivator.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs index aeda45db5..fb9075795 100644 --- a/src/dotnet/MulticoreJitActivator.cs +++ b/src/dotnet/MulticoreJitActivator.cs @@ -36,11 +36,28 @@ namespace Microsoft.DotNet.Cli { var profileOptimizationRootPath = new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath; - PathUtility.EnsureDirectory(profileOptimizationRootPath); + if (!TryEnsureDirectory(profileOptimizationRootPath)) + { + return; + } AssemblyLoadContext.Default.SetProfileOptimizationRoot(profileOptimizationRootPath); AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); } + + private bool TryEnsureDirectory(string directoryPath) + { + try + { + PathUtility.EnsureDirectory(directoryPath); + + return true; + } + catch (Exception) + { + return false; + } + } } }