// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests { public static partial class TestEnvironment { private const string ImageSharpSolutionFileName = "ImageSharp.sln"; private const string InputImagesRelativePath = @"tests\Images\Input"; private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); private static readonly Lazy RunsOnCiLazy = new Lazy( () => { bool isCi; return bool.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; }); private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); static TestEnvironment() { PrepareRemoteExecutor(); } /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// internal static string NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming /// /// Gets a value indicating whether test execution runs on CI. /// internal static bool RunsOnCI => RunsOnCiLazy.Value; internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; private static readonly FileInfo TestAssemblyFile = new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); private static string GetSolutionDirectoryFullPathImpl() { DirectoryInfo directory = TestAssemblyFile.Directory; while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) { try { directory = directory.Parent; } catch (Exception ex) { throw new Exception( $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", ex); } if (directory == null) { throw new Exception($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); } } return directory.FullName; } private static string GetFullPath(string relativePath) => Path.Combine(SolutionDirectoryFullPath, relativePath) .Replace('\\', Path.DirectorySeparatorChar); /// /// Gets the correct full path to the Input Images directory. /// internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); /// /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) /// internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); /// /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) /// internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); internal static string GetReferenceOutputFileName(string actualOutputFileName) => actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); internal static bool Is64BitProcess => IntPtr.Size == 8; internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); /// /// A dummy operation to enforce the execution of the static constructor. /// internal static void EnsureSharedInitializersDone() { } /// /// Creates the image output directory. /// /// The path. /// The path parts. /// /// The . /// internal static string CreateOutputDirectory(string path, params string[] pathParts) { path = Path.Combine(ActualOutputDirectoryFullPath, path); if (pathParts != null && pathParts.Length > 0) { path = Path.Combine(path, Path.Combine(pathParts)); } if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } return path; } /// /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe /// with the help of CorFlags.exe found in Windows SDK. /// private static void PrepareRemoteExecutor() { if (!IsFramework) { return; } string remoteExecutorConfigPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); if (File.Exists(remoteExecutorConfigPath)) { // Already initialized return; } string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; if (File.Exists(testProjectConfigPath)) { File.Copy(testProjectConfigPath, remoteExecutorConfigPath); } if (Is64BitProcess) { return; } EnsureRemoteExecutorIs32Bit(); } /// /// Locate and run CorFlags.exe /32Bit+ /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool /// private static void EnsureRemoteExecutorIs32Bit() { string windowsSdksDir = Path.Combine( Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), "Microsoft SDKs", "Windows"); FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; if (File.Exists(remoteExecutorTmpPath)) { // Already initialized return; } File.Copy(remoteExecutorPath, remoteExecutorTmpPath); string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; var si = new ProcessStartInfo() { FileName = corFlagsFile.FullName, Arguments = args, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }; using var proc = Process.Start(si); proc.WaitForExit(); string standardOutput = proc.StandardOutput.ReadToEnd(); string standardError = proc.StandardError.ReadToEnd(); if (proc.ExitCode != 0) { throw new Exception( $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); } File.Delete(remoteExecutorPath); File.Copy(remoteExecutorTmpPath, remoteExecutorPath); static FileInfo Find(DirectoryInfo root, string name) { FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); if (fi != null) { return fi; } foreach (DirectoryInfo dir in root.EnumerateDirectories()) { fi = Find(dir, name); if (fi != null) { return fi; } } return null; } } /// /// Solution borrowed from: /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 /// private static string GetNetCoreVersion() { Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; string[] assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { return assemblyPath[netCoreAppIndex + 1]; } return string.Empty; } } }