diff options
| author | Rob Mensching <rob@firegiant.com> | 2022-11-08 14:58:05 -0800 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2022-11-08 16:20:25 -0800 |
| commit | c843b47d6233153fa961c6d0e61edf7cedf255bb (patch) | |
| tree | 9eae6badd42d3badb8665b7414b4d44ca48d6ae1 /src/internal/WixBuildTools.TestSupport | |
| parent | 7e498d6348c26583972ea1cdf7d51dadc8f5b792 (diff) | |
| download | wix-c843b47d6233153fa961c6d0e61edf7cedf255bb.tar.gz wix-c843b47d6233153fa961c6d0e61edf7cedf255bb.tar.bz2 wix-c843b47d6233153fa961c6d0e61edf7cedf255bb.zip | |
Separate WixInternal content from official WixToolset namespace
Diffstat (limited to 'src/internal/WixBuildTools.TestSupport')
27 files changed, 0 insertions, 1853 deletions
diff --git a/src/internal/WixBuildTools.TestSupport/Builder.cs b/src/internal/WixBuildTools.TestSupport/Builder.cs deleted file mode 100644 index 31df0084..00000000 --- a/src/internal/WixBuildTools.TestSupport/Builder.cs +++ /dev/null | |||
| @@ -1,158 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | |||
| 9 | public class Builder | ||
| 10 | { | ||
| 11 | public Builder(string sourceFolder, Type extensionType = null, string[] bindPaths = null, string outputFile = null) | ||
| 12 | { | ||
| 13 | this.SourceFolder = sourceFolder; | ||
| 14 | this.ExtensionType = extensionType; | ||
| 15 | this.BindPaths = bindPaths; | ||
| 16 | this.OutputFile = outputFile ?? "test.msi"; | ||
| 17 | } | ||
| 18 | |||
| 19 | public string[] BindPaths { get; set; } | ||
| 20 | |||
| 21 | public Type ExtensionType { get; set; } | ||
| 22 | |||
| 23 | public string OutputFile { get; set; } | ||
| 24 | |||
| 25 | public string SourceFolder { get; } | ||
| 26 | |||
| 27 | public string[] BuildAndQuery(Action<string[]> buildFunc, params string[] tables) | ||
| 28 | { | ||
| 29 | var sourceFiles = Directory.GetFiles(this.SourceFolder, "*.wxs"); | ||
| 30 | var wxlFiles = Directory.GetFiles(this.SourceFolder, "*.wxl"); | ||
| 31 | |||
| 32 | using (var fs = new DisposableFileSystem()) | ||
| 33 | { | ||
| 34 | var intermediateFolder = fs.GetFolder(); | ||
| 35 | var outputPath = Path.Combine(intermediateFolder, "bin", this.OutputFile); | ||
| 36 | |||
| 37 | var args = new List<string> | ||
| 38 | { | ||
| 39 | "build", | ||
| 40 | "-o", outputPath, | ||
| 41 | "-intermediateFolder", intermediateFolder, | ||
| 42 | }; | ||
| 43 | |||
| 44 | if (this.ExtensionType != null) | ||
| 45 | { | ||
| 46 | args.Add("-ext"); | ||
| 47 | args.Add(Path.GetFullPath(new Uri(this.ExtensionType.Assembly.CodeBase).LocalPath)); | ||
| 48 | } | ||
| 49 | |||
| 50 | args.AddRange(sourceFiles); | ||
| 51 | |||
| 52 | foreach (var wxlFile in wxlFiles) | ||
| 53 | { | ||
| 54 | args.Add("-loc"); | ||
| 55 | args.Add(wxlFile); | ||
| 56 | } | ||
| 57 | |||
| 58 | foreach (var bindPath in this.BindPaths) | ||
| 59 | { | ||
| 60 | args.Add("-bindpath"); | ||
| 61 | args.Add(bindPath); | ||
| 62 | } | ||
| 63 | |||
| 64 | buildFunc(args.ToArray()); | ||
| 65 | |||
| 66 | return Query.QueryDatabase(outputPath, tables); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | public void BuildAndDecompileAndBuild(Action<string[]> buildFunc, Action<string[]> decompileFunc, string decompilePath) | ||
| 71 | { | ||
| 72 | var sourceFiles = Directory.GetFiles(this.SourceFolder, "*.wxs"); | ||
| 73 | var wxlFiles = Directory.GetFiles(this.SourceFolder, "*.wxl"); | ||
| 74 | |||
| 75 | using (var fs = new DisposableFileSystem()) | ||
| 76 | { | ||
| 77 | var intermediateFolder = fs.GetFolder(); | ||
| 78 | var outputFolder = Path.Combine(intermediateFolder, "bin"); | ||
| 79 | var decompileExtractFolder = Path.Combine(intermediateFolder, "decompiled", "extract"); | ||
| 80 | var decompileIntermediateFolder = Path.Combine(intermediateFolder, "decompiled", "obj"); | ||
| 81 | var decompileBuildFolder = Path.Combine(intermediateFolder, "decompiled", "bin"); | ||
| 82 | var outputPath = Path.Combine(outputFolder, this.OutputFile); | ||
| 83 | var decompileBuildPath = Path.Combine(decompileBuildFolder, this.OutputFile); | ||
| 84 | |||
| 85 | // First build. | ||
| 86 | var firstBuildArgs = new List<string> | ||
| 87 | { | ||
| 88 | "build", | ||
| 89 | "-o", outputPath, | ||
| 90 | "-intermediateFolder", intermediateFolder, | ||
| 91 | }; | ||
| 92 | |||
| 93 | if (this.ExtensionType != null) | ||
| 94 | { | ||
| 95 | firstBuildArgs.Add("-ext"); | ||
| 96 | firstBuildArgs.Add(Path.GetFullPath(new Uri(this.ExtensionType.Assembly.CodeBase).LocalPath)); | ||
| 97 | } | ||
| 98 | |||
| 99 | firstBuildArgs.AddRange(sourceFiles); | ||
| 100 | |||
| 101 | foreach (var wxlFile in wxlFiles) | ||
| 102 | { | ||
| 103 | firstBuildArgs.Add("-loc"); | ||
| 104 | firstBuildArgs.Add(wxlFile); | ||
| 105 | } | ||
| 106 | |||
| 107 | foreach (var bindPath in this.BindPaths) | ||
| 108 | { | ||
| 109 | firstBuildArgs.Add("-bindpath"); | ||
| 110 | firstBuildArgs.Add(bindPath); | ||
| 111 | } | ||
| 112 | |||
| 113 | buildFunc(firstBuildArgs.ToArray()); | ||
| 114 | |||
| 115 | // Decompile built output. | ||
| 116 | var decompileArgs = new List<string> | ||
| 117 | { | ||
| 118 | "msi", "decompile", | ||
| 119 | outputPath, | ||
| 120 | "-intermediateFolder", decompileIntermediateFolder, | ||
| 121 | "-x", decompileExtractFolder, | ||
| 122 | "-o", decompilePath | ||
| 123 | }; | ||
| 124 | |||
| 125 | if (this.ExtensionType != null) | ||
| 126 | { | ||
| 127 | decompileArgs.Add("-ext"); | ||
| 128 | decompileArgs.Add(Path.GetFullPath(new Uri(this.ExtensionType.Assembly.CodeBase).LocalPath)); | ||
| 129 | } | ||
| 130 | |||
| 131 | decompileFunc(decompileArgs.ToArray()); | ||
| 132 | |||
| 133 | // Build decompiled output. | ||
| 134 | var secondBuildArgs = new List<string> | ||
| 135 | { | ||
| 136 | "build", | ||
| 137 | decompilePath, | ||
| 138 | "-o", decompileBuildPath, | ||
| 139 | "-intermediateFolder", decompileIntermediateFolder | ||
| 140 | }; | ||
| 141 | |||
| 142 | if (this.ExtensionType != null) | ||
| 143 | { | ||
| 144 | secondBuildArgs.Add("-ext"); | ||
| 145 | secondBuildArgs.Add(Path.GetFullPath(new Uri(this.ExtensionType.Assembly.CodeBase).LocalPath)); | ||
| 146 | } | ||
| 147 | |||
| 148 | secondBuildArgs.Add("-bindpath"); | ||
| 149 | secondBuildArgs.Add(outputFolder); | ||
| 150 | |||
| 151 | secondBuildArgs.Add("-bindpath"); | ||
| 152 | secondBuildArgs.Add(decompileExtractFolder); | ||
| 153 | |||
| 154 | buildFunc(secondBuildArgs.ToArray()); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs b/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs deleted file mode 100644 index f096db72..00000000 --- a/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs +++ /dev/null | |||
| @@ -1,93 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | |||
| 9 | public class DisposableFileSystem : IDisposable | ||
| 10 | { | ||
| 11 | protected bool Disposed { get; private set; } | ||
| 12 | |||
| 13 | private List<string> CleanupPaths { get; } = new List<string>(); | ||
| 14 | |||
| 15 | public bool Keep { get; } | ||
| 16 | |||
| 17 | public DisposableFileSystem(bool keep = false) | ||
| 18 | { | ||
| 19 | this.Keep = keep; | ||
| 20 | } | ||
| 21 | |||
| 22 | protected string GetFile(bool create = false) | ||
| 23 | { | ||
| 24 | var path = Path.GetTempFileName(); | ||
| 25 | |||
| 26 | if (!create) | ||
| 27 | { | ||
| 28 | File.Delete(path); | ||
| 29 | } | ||
| 30 | |||
| 31 | this.CleanupPaths.Add(path); | ||
| 32 | |||
| 33 | return path; | ||
| 34 | } | ||
| 35 | |||
| 36 | public string GetFolder(bool create = false) | ||
| 37 | { | ||
| 38 | var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
| 39 | |||
| 40 | if (create) | ||
| 41 | { | ||
| 42 | Directory.CreateDirectory(path); | ||
| 43 | } | ||
| 44 | |||
| 45 | this.CleanupPaths.Add(path); | ||
| 46 | |||
| 47 | return path; | ||
| 48 | } | ||
| 49 | |||
| 50 | |||
| 51 | #region // IDisposable | ||
| 52 | |||
| 53 | public void Dispose() | ||
| 54 | { | ||
| 55 | this.Dispose(true); | ||
| 56 | GC.SuppressFinalize(this); | ||
| 57 | } | ||
| 58 | |||
| 59 | protected virtual void Dispose(bool disposing) | ||
| 60 | { | ||
| 61 | if (this.Disposed) | ||
| 62 | { | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | |||
| 66 | if (disposing && !this.Keep) | ||
| 67 | { | ||
| 68 | foreach (var path in this.CleanupPaths) | ||
| 69 | { | ||
| 70 | try | ||
| 71 | { | ||
| 72 | if (File.Exists(path)) | ||
| 73 | { | ||
| 74 | File.Delete(path); | ||
| 75 | } | ||
| 76 | else if (Directory.Exists(path)) | ||
| 77 | { | ||
| 78 | Directory.Delete(path, true); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | catch | ||
| 82 | { | ||
| 83 | // Best effort delete, so ignore any failures. | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | this.Disposed = true; | ||
| 89 | } | ||
| 90 | |||
| 91 | #endregion | ||
| 92 | } | ||
| 93 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs b/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs deleted file mode 100644 index 82391178..00000000 --- a/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | |||
| 9 | public class DotnetRunner : ExternalExecutable | ||
| 10 | { | ||
| 11 | private static readonly object InitLock = new object(); | ||
| 12 | private static bool Initialized; | ||
| 13 | private static DotnetRunner Instance; | ||
| 14 | |||
| 15 | public static ExternalExecutableResult Execute(string command, string[] arguments = null) => | ||
| 16 | InitAndExecute(command, arguments); | ||
| 17 | |||
| 18 | private static ExternalExecutableResult InitAndExecute(string command, string[] arguments) | ||
| 19 | { | ||
| 20 | lock (InitLock) | ||
| 21 | { | ||
| 22 | if (!Initialized) | ||
| 23 | { | ||
| 24 | Initialized = true; | ||
| 25 | var dotnetPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); | ||
| 26 | if (String.IsNullOrEmpty(dotnetPath) || !File.Exists(dotnetPath)) | ||
| 27 | { | ||
| 28 | dotnetPath = "dotnet"; | ||
| 29 | } | ||
| 30 | |||
| 31 | Instance = new DotnetRunner(dotnetPath); | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | return Instance.ExecuteCore(command, arguments); | ||
| 36 | } | ||
| 37 | |||
| 38 | private DotnetRunner(string exePath) : base(exePath) { } | ||
| 39 | |||
| 40 | private ExternalExecutableResult ExecuteCore(string command, string[] arguments) | ||
| 41 | { | ||
| 42 | var total = new List<string> | ||
| 43 | { | ||
| 44 | command, | ||
| 45 | }; | ||
| 46 | |||
| 47 | if (arguments != null) | ||
| 48 | { | ||
| 49 | total.AddRange(arguments); | ||
| 50 | } | ||
| 51 | |||
| 52 | var args = CombineArguments(total); | ||
| 53 | var mergeErrorIntoOutput = true; | ||
| 54 | return this.Run(args, mergeErrorIntoOutput); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs deleted file mode 100644 index 4a932645..00000000 --- a/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs +++ /dev/null | |||
| @@ -1,374 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Concurrent; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.ComponentModel; | ||
| 9 | using System.Diagnostics; | ||
| 10 | using System.IO; | ||
| 11 | using System.Runtime.InteropServices; | ||
| 12 | using System.Text; | ||
| 13 | using System.Threading.Tasks; | ||
| 14 | using Microsoft.Win32.SafeHandles; | ||
| 15 | |||
| 16 | public abstract class ExternalExecutable | ||
| 17 | { | ||
| 18 | private readonly string exePath; | ||
| 19 | |||
| 20 | protected ExternalExecutable(string exePath) | ||
| 21 | { | ||
| 22 | this.exePath = exePath; | ||
| 23 | } | ||
| 24 | |||
| 25 | protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null) | ||
| 26 | { | ||
| 27 | // https://github.com/dotnet/runtime/issues/58492 | ||
| 28 | // Process.Start doesn't currently support starting a process with a long path, | ||
| 29 | // but the way to support long paths doesn't support searching for the executable if it was a relative path. | ||
| 30 | // Avoid the managed way of doing this even if the target isn't a long path to help verify that the native way works. | ||
| 31 | if (!Path.IsPathRooted(this.exePath)) | ||
| 32 | { | ||
| 33 | return this.RunManaged(args, mergeErrorIntoOutput, workingDirectory); | ||
| 34 | } | ||
| 35 | |||
| 36 | // https://web.archive.org/web/20150331190801/https://support.microsoft.com/en-us/kb/190351 | ||
| 37 | var commandLine = $"\"{this.exePath}\" {args}"; | ||
| 38 | var currentDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath); | ||
| 39 | if (String.IsNullOrEmpty(currentDirectory)) | ||
| 40 | { | ||
| 41 | currentDirectory = null; | ||
| 42 | } | ||
| 43 | var processInfo = new PROCESS_INFORMATION(); | ||
| 44 | var startInfo = new STARTUPINFOW | ||
| 45 | { | ||
| 46 | cb = Marshal.SizeOf(typeof(STARTUPINFOW)), | ||
| 47 | dwFlags = StartupInfoFlags.STARTF_FORCEOFFFEEDBACK | StartupInfoFlags.STARTF_USESTDHANDLES, | ||
| 48 | hStdInput = GetStdHandle(StdHandleType.STD_INPUT_HANDLE), | ||
| 49 | }; | ||
| 50 | SafeFileHandle hStdOutputParent = null; | ||
| 51 | SafeFileHandle hStdErrorParent = null; | ||
| 52 | |||
| 53 | try | ||
| 54 | { | ||
| 55 | CreatePipeForProcess(out hStdOutputParent, out startInfo.hStdOutput); | ||
| 56 | |||
| 57 | if (!mergeErrorIntoOutput) | ||
| 58 | { | ||
| 59 | CreatePipeForProcess(out hStdErrorParent, out startInfo.hStdError); | ||
| 60 | } | ||
| 61 | else | ||
| 62 | { | ||
| 63 | if (!DuplicateHandle(GetCurrentProcess(), startInfo.hStdOutput, GetCurrentProcess(), out startInfo.hStdError, 0, true, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) | ||
| 64 | { | ||
| 65 | throw new Win32Exception(); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | if (!CreateProcessW(this.exePath, commandLine, IntPtr.Zero, IntPtr.Zero, true, CreateProcessFlags.CREATE_NO_WINDOW, IntPtr.Zero, | ||
| 70 | currentDirectory, ref startInfo, ref processInfo)) | ||
| 71 | { | ||
| 72 | throw new Win32Exception(); | ||
| 73 | } | ||
| 74 | |||
| 75 | startInfo.Dispose(); | ||
| 76 | |||
| 77 | return GetResultFromNative(mergeErrorIntoOutput, hStdOutputParent, hStdErrorParent, processInfo.hProcess, this.exePath, args); | ||
| 78 | } | ||
| 79 | finally | ||
| 80 | { | ||
| 81 | hStdErrorParent?.Dispose(); | ||
| 82 | hStdOutputParent?.Dispose(); | ||
| 83 | |||
| 84 | startInfo.Dispose(); | ||
| 85 | processInfo.Dispose(); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | private static ExternalExecutableResult GetResultFromNative(bool mergeErrorIntoOutput, SafeFileHandle hStdOutputParent, SafeFileHandle hStdErrorParent, IntPtr hProcess, string fileName, string args) | ||
| 90 | { | ||
| 91 | using (var outputStream = new StreamReader(new FileStream(hStdOutputParent, FileAccess.Read))) | ||
| 92 | using (var errorStream = mergeErrorIntoOutput ? null : new StreamReader(new FileStream(hStdErrorParent, FileAccess.Read))) | ||
| 93 | { | ||
| 94 | var outputTask = Task.Run(() => ReadProcessStreamLines(outputStream)); | ||
| 95 | var errorTask = Task.Run(() => ReadProcessStreamLines(errorStream)); | ||
| 96 | |||
| 97 | while (!outputTask.Wait(100) || !errorTask.Wait(100)) { Task.Yield(); } | ||
| 98 | var standardOutput = outputTask.Result; | ||
| 99 | var standardError = errorTask.Result; | ||
| 100 | |||
| 101 | if (WaitForSingleObject(hProcess, -1) != 0) | ||
| 102 | { | ||
| 103 | throw new Win32Exception(); | ||
| 104 | } | ||
| 105 | |||
| 106 | if (!GetExitCodeProcess(hProcess, out var exitCode)) | ||
| 107 | { | ||
| 108 | throw new Win32Exception(); | ||
| 109 | } | ||
| 110 | |||
| 111 | return new ExternalExecutableResult | ||
| 112 | { | ||
| 113 | ExitCode = exitCode, | ||
| 114 | StandardError = standardError, | ||
| 115 | StandardOutput = standardOutput, | ||
| 116 | FileName = fileName, | ||
| 117 | Arguments = args, | ||
| 118 | }; | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | private static string[] ReadProcessStreamLines(StreamReader streamReader) | ||
| 123 | { | ||
| 124 | if (streamReader == null) | ||
| 125 | { | ||
| 126 | return null; | ||
| 127 | } | ||
| 128 | |||
| 129 | var lines = new List<string>(); | ||
| 130 | while (true) | ||
| 131 | { | ||
| 132 | var line = streamReader.ReadLine(); | ||
| 133 | if (line == null) | ||
| 134 | { | ||
| 135 | break; | ||
| 136 | } | ||
| 137 | |||
| 138 | lines.Add(line); | ||
| 139 | } | ||
| 140 | |||
| 141 | return lines.ToArray(); | ||
| 142 | } | ||
| 143 | |||
| 144 | protected ExternalExecutableResult RunManaged(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null) | ||
| 145 | { | ||
| 146 | var startInfo = new ProcessStartInfo(this.exePath, args) | ||
| 147 | { | ||
| 148 | CreateNoWindow = true, | ||
| 149 | RedirectStandardError = true, | ||
| 150 | RedirectStandardOutput = true, | ||
| 151 | UseShellExecute = false, | ||
| 152 | WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath), | ||
| 153 | }; | ||
| 154 | |||
| 155 | using (var process = Process.Start(startInfo)) | ||
| 156 | { | ||
| 157 | // This implementation of merging the streams does not guarantee that lines are retrieved in the same order that they were written. | ||
| 158 | // If the process is simultaneously writing to both streams, this is impossible to do anyway. | ||
| 159 | var standardOutput = new ConcurrentQueue<string>(); | ||
| 160 | var standardError = mergeErrorIntoOutput ? standardOutput : new ConcurrentQueue<string>(); | ||
| 161 | |||
| 162 | process.ErrorDataReceived += (s, e) => { if (e.Data != null) { standardError.Enqueue(e.Data); } }; | ||
| 163 | process.OutputDataReceived += (s, e) => { if (e.Data != null) { standardOutput.Enqueue(e.Data); } }; | ||
| 164 | |||
| 165 | process.BeginErrorReadLine(); | ||
| 166 | process.BeginOutputReadLine(); | ||
| 167 | |||
| 168 | process.WaitForExit(); | ||
| 169 | |||
| 170 | return new ExternalExecutableResult | ||
| 171 | { | ||
| 172 | ExitCode = process.ExitCode, | ||
| 173 | StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(), | ||
| 174 | StandardOutput = standardOutput.ToArray(), | ||
| 175 | FileName = this.exePath, | ||
| 176 | Arguments = args, | ||
| 177 | }; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | // This is internal because it assumes backslashes aren't used as escape characters and there aren't any double quotes. | ||
| 182 | internal static string CombineArguments(IEnumerable<string> arguments) | ||
| 183 | { | ||
| 184 | if (arguments == null) | ||
| 185 | { | ||
| 186 | return null; | ||
| 187 | } | ||
| 188 | |||
| 189 | var sb = new StringBuilder(); | ||
| 190 | |||
| 191 | foreach (var arg in arguments) | ||
| 192 | { | ||
| 193 | if (sb.Length > 0) | ||
| 194 | { | ||
| 195 | sb.Append(' '); | ||
| 196 | } | ||
| 197 | |||
| 198 | if (arg.IndexOf(' ') > -1) | ||
| 199 | { | ||
| 200 | sb.Append("\""); | ||
| 201 | sb.Append(arg); | ||
| 202 | sb.Append("\""); | ||
| 203 | } | ||
| 204 | else | ||
| 205 | { | ||
| 206 | sb.Append(arg); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | return sb.ToString(); | ||
| 211 | } | ||
| 212 | |||
| 213 | private static void CreatePipeForProcess(out SafeFileHandle hReadPipe, out IntPtr hWritePipe) | ||
| 214 | { | ||
| 215 | var securityAttributes = new SECURITY_ATTRIBUTES | ||
| 216 | { | ||
| 217 | nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)), | ||
| 218 | bInheritHandle = true, | ||
| 219 | }; | ||
| 220 | |||
| 221 | if (!CreatePipe(out var hReadTemp, out hWritePipe, ref securityAttributes, 0)) | ||
| 222 | { | ||
| 223 | throw new Win32Exception(); | ||
| 224 | } | ||
| 225 | |||
| 226 | // Only the handle passed to the process should be inheritable, so have to duplicate the other handle to get an uninheritable one. | ||
| 227 | if (!DuplicateHandle(GetCurrentProcess(), hReadTemp, GetCurrentProcess(), out var hReadPipePtr, 0, false, DuplicateHandleOptions.DUPLICATE_CLOSE_SOURCE | DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) | ||
| 228 | { | ||
| 229 | throw new Win32Exception(); | ||
| 230 | } | ||
| 231 | |||
| 232 | hReadPipe = new SafeFileHandle(hReadPipePtr, true); | ||
| 233 | } | ||
| 234 | |||
| 235 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 236 | private extern static IntPtr GetStdHandle(StdHandleType nStdHandle); | ||
| 237 | |||
| 238 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 239 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 240 | private extern static bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); | ||
| 241 | |||
| 242 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 243 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 244 | private extern static bool CreateProcessW( | ||
| 245 | string lpApplicationName, | ||
| 246 | string lpCommandLine, | ||
| 247 | IntPtr lpProcessAttributes, | ||
| 248 | IntPtr lpThreadAttributes, | ||
| 249 | [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, | ||
| 250 | CreateProcessFlags dwCreationFlags, | ||
| 251 | IntPtr lpEnvironment, | ||
| 252 | string lpCurrentDirectory, | ||
| 253 | ref STARTUPINFOW lpStartupInfo, | ||
| 254 | ref PROCESS_INFORMATION lpProcessInformation); | ||
| 255 | |||
| 256 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
| 257 | private extern static IntPtr GetCurrentProcess(); | ||
| 258 | |||
| 259 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 260 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 261 | private extern static bool GetExitCodeProcess(IntPtr hHandle, out int lpExitCode); | ||
| 262 | |||
| 263 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 264 | private extern static int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds); | ||
| 265 | |||
| 266 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 267 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 268 | private extern static bool CloseHandle(IntPtr hObject); | ||
| 269 | |||
| 270 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
| 271 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 272 | private extern static bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateHandleOptions dwOptions); | ||
| 273 | |||
| 274 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
| 275 | private struct SECURITY_ATTRIBUTES | ||
| 276 | { | ||
| 277 | public int nLength; | ||
| 278 | public IntPtr lpSecurityDescriptor; | ||
| 279 | [MarshalAs(UnmanagedType.Bool)] | ||
| 280 | public bool bInheritHandle; | ||
| 281 | } | ||
| 282 | |||
| 283 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
| 284 | private struct STARTUPINFOW | ||
| 285 | { | ||
| 286 | public int cb; | ||
| 287 | public string lpReserved; | ||
| 288 | public string lpDesktop; | ||
| 289 | public string lpTitle; | ||
| 290 | public int dwX; | ||
| 291 | public int dwY; | ||
| 292 | public int dwXSize; | ||
| 293 | public int dwYSize; | ||
| 294 | public int dwXCountChars; | ||
| 295 | public int dwYCountChars; | ||
| 296 | public int dwFillAttribute; | ||
| 297 | public StartupInfoFlags dwFlags; | ||
| 298 | public short wShowWindow; | ||
| 299 | public short cbReserved2; | ||
| 300 | public IntPtr lpReserved2; | ||
| 301 | public IntPtr hStdInput; | ||
| 302 | public IntPtr hStdOutput; | ||
| 303 | public IntPtr hStdError; | ||
| 304 | |||
| 305 | public void Dispose() | ||
| 306 | { | ||
| 307 | // This makes assumptions based on how it's used above. | ||
| 308 | if (this.hStdError != IntPtr.Zero) | ||
| 309 | { | ||
| 310 | CloseHandle(this.hStdError); | ||
| 311 | this.hStdError = IntPtr.Zero; | ||
| 312 | } | ||
| 313 | |||
| 314 | if (this.hStdOutput != IntPtr.Zero) | ||
| 315 | { | ||
| 316 | CloseHandle(this.hStdOutput); | ||
| 317 | this.hStdOutput = IntPtr.Zero; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
| 323 | private struct PROCESS_INFORMATION | ||
| 324 | { | ||
| 325 | public IntPtr hProcess; | ||
| 326 | public IntPtr hThread; | ||
| 327 | public int dwProcessId; | ||
| 328 | public int dwThreadId; | ||
| 329 | |||
| 330 | public void Dispose() | ||
| 331 | { | ||
| 332 | if (this.hProcess != IntPtr.Zero) | ||
| 333 | { | ||
| 334 | CloseHandle(this.hProcess); | ||
| 335 | this.hProcess = IntPtr.Zero; | ||
| 336 | } | ||
| 337 | |||
| 338 | if (this.hThread != IntPtr.Zero) | ||
| 339 | { | ||
| 340 | CloseHandle(this.hThread); | ||
| 341 | this.hThread = IntPtr.Zero; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | private enum StdHandleType | ||
| 347 | { | ||
| 348 | STD_INPUT_HANDLE = -10, | ||
| 349 | STD_OUTPUT_HANDLE = -11, | ||
| 350 | STD_ERROR_HANDLE = -12, | ||
| 351 | } | ||
| 352 | |||
| 353 | [Flags] | ||
| 354 | private enum CreateProcessFlags | ||
| 355 | { | ||
| 356 | None = 0x0, | ||
| 357 | CREATE_NO_WINDOW = 0x08000000, | ||
| 358 | } | ||
| 359 | |||
| 360 | [Flags] | ||
| 361 | private enum StartupInfoFlags | ||
| 362 | { | ||
| 363 | None = 0x0, | ||
| 364 | STARTF_FORCEOFFFEEDBACK = 0x80, | ||
| 365 | STARTF_USESTDHANDLES = 0x100, | ||
| 366 | } | ||
| 367 | |||
| 368 | private enum DuplicateHandleOptions | ||
| 369 | { | ||
| 370 | DUPLICATE_CLOSE_SOURCE = 1, | ||
| 371 | DUPLICATE_SAME_ACCESS = 2, | ||
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs deleted file mode 100644 index 950ee4bd..00000000 --- a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System.Diagnostics; | ||
| 6 | |||
| 7 | public class ExternalExecutableResult | ||
| 8 | { | ||
| 9 | public int ExitCode { get; set; } | ||
| 10 | |||
| 11 | public string[] StandardError { get; set; } | ||
| 12 | |||
| 13 | public string[] StandardOutput { get; set; } | ||
| 14 | |||
| 15 | public string FileName { get; set; } | ||
| 16 | |||
| 17 | public string Arguments { get; set; } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs b/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs deleted file mode 100644 index 20545970..00000000 --- a/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System.Collections; | ||
| 6 | using System.Text; | ||
| 7 | using Microsoft.Build.Framework; | ||
| 8 | |||
| 9 | public class FakeBuildEngine : IBuildEngine | ||
| 10 | { | ||
| 11 | private readonly StringBuilder output = new StringBuilder(); | ||
| 12 | |||
| 13 | public int ColumnNumberOfTaskNode => 0; | ||
| 14 | |||
| 15 | public bool ContinueOnError => false; | ||
| 16 | |||
| 17 | public int LineNumberOfTaskNode => 0; | ||
| 18 | |||
| 19 | public string ProjectFileOfTaskNode => "fake_wix.targets"; | ||
| 20 | |||
| 21 | public string Output => this.output.ToString(); | ||
| 22 | |||
| 23 | public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => throw new System.NotImplementedException(); | ||
| 24 | |||
| 25 | public void LogCustomEvent(CustomBuildEventArgs e) => this.output.AppendLine(e.Message); | ||
| 26 | |||
| 27 | public void LogErrorEvent(BuildErrorEventArgs e) => this.output.AppendLine(e.Message); | ||
| 28 | |||
| 29 | public void LogMessageEvent(BuildMessageEventArgs e) => this.output.AppendLine(e.Message); | ||
| 30 | |||
| 31 | public void LogWarningEvent(BuildWarningEventArgs e) => this.output.AppendLine(e.Message); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs b/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs deleted file mode 100644 index ac7caf0e..00000000 --- a/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs +++ /dev/null | |||
| @@ -1,112 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | |||
| 9 | public class MsbuildRunner : ExternalExecutable | ||
| 10 | { | ||
| 11 | private static readonly string VswhereFindArguments = "-property installationPath -version [17.0,18.0)"; | ||
| 12 | private static readonly string MsbuildCurrentRelativePath = @"MSBuild\Current\Bin\MSBuild.exe"; | ||
| 13 | private static readonly string MsbuildCurrentRelativePath64 = @"MSBuild\Current\Bin\amd64\MSBuild.exe"; | ||
| 14 | |||
| 15 | private static readonly object InitLock = new object(); | ||
| 16 | |||
| 17 | private static bool Initialized; | ||
| 18 | private static MsbuildRunner MsbuildCurrentRunner; | ||
| 19 | private static MsbuildRunner MsbuildCurrentRunner64; | ||
| 20 | |||
| 21 | public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null, bool x64 = false) => | ||
| 22 | InitAndExecute(String.Empty, projectPath, arguments, x64); | ||
| 23 | |||
| 24 | public static MsbuildRunnerResult ExecuteWithMsbuildCurrent(string projectPath, string[] arguments = null, bool x64 = false) => | ||
| 25 | InitAndExecute("Current", projectPath, arguments, x64); | ||
| 26 | |||
| 27 | private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments, bool x64) | ||
| 28 | { | ||
| 29 | lock (InitLock) | ||
| 30 | { | ||
| 31 | if (!Initialized) | ||
| 32 | { | ||
| 33 | Initialized = true; | ||
| 34 | var vswhereResult = VswhereRunner.Execute(VswhereFindArguments, true); | ||
| 35 | if (vswhereResult.ExitCode != 0) | ||
| 36 | { | ||
| 37 | throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {vswhereResult.ExitCode}. Output:\r\n{String.Join("\r\n", vswhereResult.StandardOutput)}"); | ||
| 38 | } | ||
| 39 | |||
| 40 | string msbuildCurrentPath = null; | ||
| 41 | string msbuildCurrentPath64 = null; | ||
| 42 | |||
| 43 | foreach (var installPath in vswhereResult.StandardOutput) | ||
| 44 | { | ||
| 45 | if (msbuildCurrentPath == null) | ||
| 46 | { | ||
| 47 | var path = Path.Combine(installPath, MsbuildCurrentRelativePath); | ||
| 48 | if (File.Exists(path)) | ||
| 49 | { | ||
| 50 | msbuildCurrentPath = path; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | if (msbuildCurrentPath64 == null) | ||
| 55 | { | ||
| 56 | var path = Path.Combine(installPath, MsbuildCurrentRelativePath64); | ||
| 57 | if (File.Exists(path)) | ||
| 58 | { | ||
| 59 | msbuildCurrentPath64 = path; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | if (msbuildCurrentPath != null) | ||
| 65 | { | ||
| 66 | MsbuildCurrentRunner = new MsbuildRunner(msbuildCurrentPath); | ||
| 67 | } | ||
| 68 | |||
| 69 | if (msbuildCurrentPath64 != null) | ||
| 70 | { | ||
| 71 | MsbuildCurrentRunner64 = new MsbuildRunner(msbuildCurrentPath64); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | MsbuildRunner runner = x64 ? MsbuildCurrentRunner64 : MsbuildCurrentRunner; | ||
| 77 | |||
| 78 | if (runner == null) | ||
| 79 | { | ||
| 80 | throw new InvalidOperationException($"Failed to find an installed{(x64 ? " 64-bit" : String.Empty)} MSBuild{msbuildVersion}"); | ||
| 81 | } | ||
| 82 | |||
| 83 | return runner.ExecuteCore(projectPath, arguments); | ||
| 84 | } | ||
| 85 | |||
| 86 | private MsbuildRunner(string exePath) : base(exePath) { } | ||
| 87 | |||
| 88 | private MsbuildRunnerResult ExecuteCore(string projectPath, string[] arguments) | ||
| 89 | { | ||
| 90 | var total = new List<string> | ||
| 91 | { | ||
| 92 | projectPath, | ||
| 93 | }; | ||
| 94 | |||
| 95 | if (arguments != null) | ||
| 96 | { | ||
| 97 | total.AddRange(arguments); | ||
| 98 | } | ||
| 99 | |||
| 100 | var args = CombineArguments(total); | ||
| 101 | var mergeErrorIntoOutput = true; | ||
| 102 | var workingFolder = Path.GetDirectoryName(projectPath); | ||
| 103 | var result = this.Run(args, mergeErrorIntoOutput, workingFolder); | ||
| 104 | |||
| 105 | return new MsbuildRunnerResult | ||
| 106 | { | ||
| 107 | ExitCode = result.ExitCode, | ||
| 108 | Output = result.StandardOutput, | ||
| 109 | }; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs b/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs deleted file mode 100644 index fb61122d..00000000 --- a/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using Xunit; | ||
| 7 | |||
| 8 | public class MsbuildRunnerResult | ||
| 9 | { | ||
| 10 | public int ExitCode { get; set; } | ||
| 11 | |||
| 12 | public string[] Output { get; set; } | ||
| 13 | |||
| 14 | public void AssertSuccess() | ||
| 15 | { | ||
| 16 | Assert.True(0 == this.ExitCode, $"MSBuild failed unexpectedly. Output:{Environment.NewLine}{String.Join(Environment.NewLine, this.Output)}"); | ||
| 17 | } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildUtilities.cs b/src/internal/WixBuildTools.TestSupport/MsbuildUtilities.cs deleted file mode 100644 index 3271cc20..00000000 --- a/src/internal/WixBuildTools.TestSupport/MsbuildUtilities.cs +++ /dev/null | |||
| @@ -1,101 +0,0 @@ | |||
| 1 | // Copyright(c) .NET Foundation and contributors.All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.Sdk | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | |||
| 11 | public enum BuildSystem | ||
| 12 | { | ||
| 13 | DotNetCoreSdk, | ||
| 14 | MSBuild, | ||
| 15 | MSBuild64, | ||
| 16 | } | ||
| 17 | |||
| 18 | public static class MsbuildUtilities | ||
| 19 | { | ||
| 20 | public static MsbuildRunnerResult BuildProject(BuildSystem buildSystem, string projectPath, string[] arguments = null, string configuration = "Release", string verbosityLevel = "normal", bool suppressValidation = true) | ||
| 21 | { | ||
| 22 | var allArgs = new List<string> | ||
| 23 | { | ||
| 24 | $"-verbosity:{verbosityLevel}", | ||
| 25 | $"-p:Configuration={configuration}", | ||
| 26 | $"-p:SuppressValidation={suppressValidation}", | ||
| 27 | // Node reuse means that child msbuild processes can stay around after the build completes. | ||
| 28 | // Under that scenario, the root msbuild does not reliably close its streams which causes us to hang. | ||
| 29 | "-nr:false", | ||
| 30 | $"-bl:{Path.ChangeExtension(projectPath, ".binlog")}" | ||
| 31 | }; | ||
| 32 | |||
| 33 | if (arguments != null) | ||
| 34 | { | ||
| 35 | allArgs.AddRange(arguments); | ||
| 36 | } | ||
| 37 | |||
| 38 | switch (buildSystem) | ||
| 39 | { | ||
| 40 | case BuildSystem.DotNetCoreSdk: | ||
| 41 | { | ||
| 42 | allArgs.Add(projectPath); | ||
| 43 | var result = DotnetRunner.Execute("msbuild", allArgs.ToArray()); | ||
| 44 | return new MsbuildRunnerResult | ||
| 45 | { | ||
| 46 | ExitCode = result.ExitCode, | ||
| 47 | Output = result.StandardOutput, | ||
| 48 | }; | ||
| 49 | } | ||
| 50 | case BuildSystem.MSBuild: | ||
| 51 | case BuildSystem.MSBuild64: | ||
| 52 | { | ||
| 53 | return MsbuildRunner.Execute(projectPath, allArgs.ToArray(), buildSystem == BuildSystem.MSBuild64); | ||
| 54 | } | ||
| 55 | default: | ||
| 56 | { | ||
| 57 | throw new NotImplementedException(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | public static string GetQuotedPropertySwitch(BuildSystem buildSystem, string propertyName, string valueToQuote) | ||
| 63 | { | ||
| 64 | switch (buildSystem) | ||
| 65 | { | ||
| 66 | case BuildSystem.DotNetCoreSdk: | ||
| 67 | { | ||
| 68 | // If the value ends with a backslash, double-escape it (it should end up with four backslashes). | ||
| 69 | if (valueToQuote?.EndsWith("\\") == true) | ||
| 70 | { | ||
| 71 | valueToQuote += @"\\\"; | ||
| 72 | } | ||
| 73 | |||
| 74 | return $"-p:{propertyName}=\\\"{valueToQuote}\\\""; | ||
| 75 | } | ||
| 76 | case BuildSystem.MSBuild: | ||
| 77 | case BuildSystem.MSBuild64: | ||
| 78 | { | ||
| 79 | // If the value ends with a backslash, escape it. | ||
| 80 | if (valueToQuote?.EndsWith("\\") == true) | ||
| 81 | { | ||
| 82 | valueToQuote += @"\"; | ||
| 83 | } | ||
| 84 | |||
| 85 | return $"-p:{propertyName}=\"{valueToQuote}\""; | ||
| 86 | } | ||
| 87 | default: | ||
| 88 | { | ||
| 89 | throw new NotImplementedException(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | public static IEnumerable<string> GetToolCommandLines(MsbuildRunnerResult result, string toolName, string operation, BuildSystem buildSystem) | ||
| 95 | { | ||
| 96 | var expectedToolExe = buildSystem == BuildSystem.DotNetCoreSdk ? $"{toolName}.dll\"" : $"{toolName}.exe"; | ||
| 97 | var expectedToolCommand = $"{expectedToolExe} {operation}"; | ||
| 98 | return result.Output.Where(line => line.Contains(expectedToolCommand)); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/Pushd.cs b/src/internal/WixBuildTools.TestSupport/Pushd.cs deleted file mode 100644 index d0545215..00000000 --- a/src/internal/WixBuildTools.TestSupport/Pushd.cs +++ /dev/null | |||
| @@ -1,46 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | |||
| 8 | public class Pushd : IDisposable | ||
| 9 | { | ||
| 10 | protected bool Disposed { get; private set; } | ||
| 11 | |||
| 12 | public Pushd(string path) | ||
| 13 | { | ||
| 14 | this.PreviousDirectory = Directory.GetCurrentDirectory(); | ||
| 15 | |||
| 16 | Directory.SetCurrentDirectory(path); | ||
| 17 | } | ||
| 18 | |||
| 19 | public string PreviousDirectory { get; } | ||
| 20 | |||
| 21 | #region // IDisposable | ||
| 22 | |||
| 23 | public void Dispose() | ||
| 24 | { | ||
| 25 | this.Dispose(true); | ||
| 26 | GC.SuppressFinalize(this); | ||
| 27 | } | ||
| 28 | |||
| 29 | protected virtual void Dispose(bool disposing) | ||
| 30 | { | ||
| 31 | if (this.Disposed) | ||
| 32 | { | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | if (disposing) | ||
| 37 | { | ||
| 38 | Directory.SetCurrentDirectory(this.PreviousDirectory); | ||
| 39 | } | ||
| 40 | |||
| 41 | this.Disposed = true; | ||
| 42 | } | ||
| 43 | |||
| 44 | #endregion | ||
| 45 | } | ||
| 46 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/Query.cs b/src/internal/WixBuildTools.TestSupport/Query.cs deleted file mode 100644 index 101a8890..00000000 --- a/src/internal/WixBuildTools.TestSupport/Query.cs +++ /dev/null | |||
| @@ -1,172 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Text; | ||
| 10 | using WixToolset.Dtf.Compression.Cab; | ||
| 11 | using WixToolset.Dtf.WindowsInstaller; | ||
| 12 | |||
| 13 | public class Query | ||
| 14 | { | ||
| 15 | public static string[] QueryDatabase(string path, string[] tables) | ||
| 16 | { | ||
| 17 | var results = new List<string>(); | ||
| 18 | var resultsByTable = QueryDatabaseByTable(path, tables); | ||
| 19 | var sortedTables = tables.ToList(); | ||
| 20 | sortedTables.Sort(); | ||
| 21 | foreach (var tableName in sortedTables) | ||
| 22 | { | ||
| 23 | var rows = resultsByTable[tableName]; | ||
| 24 | rows?.ForEach(r => results.Add($"{tableName}:{r}")); | ||
| 25 | } | ||
| 26 | return results.ToArray(); | ||
| 27 | } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Returns rows from requested tables formatted to facilitate testing. | ||
| 31 | /// If the table did not exist in the database, its list will be null. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="path"></param> | ||
| 34 | /// <param name="tables"></param> | ||
| 35 | /// <returns></returns> | ||
| 36 | public static Dictionary<string, List<string>> QueryDatabaseByTable(string path, string[] tables) | ||
| 37 | { | ||
| 38 | var results = new Dictionary<string, List<string>>(); | ||
| 39 | |||
| 40 | if (tables?.Length > 0) | ||
| 41 | { | ||
| 42 | var sb = new StringBuilder(); | ||
| 43 | using (var db = new Database(path)) | ||
| 44 | { | ||
| 45 | foreach (var table in tables) | ||
| 46 | { | ||
| 47 | if (table == "_SummaryInformation") | ||
| 48 | { | ||
| 49 | var entries = new List<string>(); | ||
| 50 | results.Add(table, entries); | ||
| 51 | |||
| 52 | entries.Add($"Title\t{db.SummaryInfo.Title}"); | ||
| 53 | entries.Add($"Subject\t{db.SummaryInfo.Subject}"); | ||
| 54 | entries.Add($"Author\t{db.SummaryInfo.Author}"); | ||
| 55 | entries.Add($"Keywords\t{db.SummaryInfo.Keywords}"); | ||
| 56 | entries.Add($"Comments\t{db.SummaryInfo.Comments}"); | ||
| 57 | entries.Add($"Template\t{db.SummaryInfo.Template}"); | ||
| 58 | entries.Add($"CodePage\t{db.SummaryInfo.CodePage}"); | ||
| 59 | entries.Add($"PageCount\t{db.SummaryInfo.PageCount}"); | ||
| 60 | entries.Add($"WordCount\t{db.SummaryInfo.WordCount}"); | ||
| 61 | entries.Add($"CharacterCount\t{db.SummaryInfo.CharacterCount}"); | ||
| 62 | entries.Add($"Security\t{db.SummaryInfo.Security}"); | ||
| 63 | |||
| 64 | continue; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!db.IsTablePersistent(table)) | ||
| 68 | { | ||
| 69 | results.Add(table, null); | ||
| 70 | continue; | ||
| 71 | } | ||
| 72 | |||
| 73 | var rows = new List<string>(); | ||
| 74 | results.Add(table, rows); | ||
| 75 | |||
| 76 | using (var view = db.OpenView("SELECT * FROM `{0}`", table)) | ||
| 77 | { | ||
| 78 | view.Execute(); | ||
| 79 | |||
| 80 | Record record; | ||
| 81 | while ((record = view.Fetch()) != null) | ||
| 82 | { | ||
| 83 | sb.Clear(); | ||
| 84 | |||
| 85 | using (record) | ||
| 86 | { | ||
| 87 | for (var i = 0; i < record.FieldCount; ++i) | ||
| 88 | { | ||
| 89 | if (i > 0) | ||
| 90 | { | ||
| 91 | sb.Append("\t"); | ||
| 92 | } | ||
| 93 | |||
| 94 | sb.Append(record[i + 1]?.ToString()); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | rows.Add(sb.ToString()); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | rows.Sort(); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | return results; | ||
| 107 | } | ||
| 108 | |||
| 109 | public static CabFileInfo[] GetCabinetFiles(string path) | ||
| 110 | { | ||
| 111 | var cab = new CabInfo(path); | ||
| 112 | |||
| 113 | var result = cab.GetFiles(); | ||
| 114 | |||
| 115 | return result.Select(c => c).ToArray(); | ||
| 116 | } | ||
| 117 | |||
| 118 | public static void ExtractStream(string path, string streamName, string outputPath) | ||
| 119 | { | ||
| 120 | Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); | ||
| 121 | |||
| 122 | using (var db = new Database(path)) | ||
| 123 | using (var view = db.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streamName)) | ||
| 124 | { | ||
| 125 | view.Execute(); | ||
| 126 | |||
| 127 | using (var record = view.Fetch()) | ||
| 128 | { | ||
| 129 | record.GetStream(1, outputPath); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | public static void ExtractSubStorage(string path, string subStorageName, string outputPath) | ||
| 135 | { | ||
| 136 | Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); | ||
| 137 | |||
| 138 | using (var db = new Database(path)) | ||
| 139 | using (var view = db.OpenView("SELECT `Name`, `Data` FROM `_Storages` WHERE `Name` = '{0}'", subStorageName)) | ||
| 140 | { | ||
| 141 | view.Execute(); | ||
| 142 | |||
| 143 | using (var record = view.Fetch()) | ||
| 144 | { | ||
| 145 | var name = record.GetString(1); | ||
| 146 | record.GetStream(2, outputPath); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | public static string[] GetSubStorageNames(string path) | ||
| 152 | { | ||
| 153 | var result = new List<string>(); | ||
| 154 | |||
| 155 | using (var db = new Database(path)) | ||
| 156 | using (var view = db.OpenView("SELECT `Name` FROM `_Storages`")) | ||
| 157 | { | ||
| 158 | view.Execute(); | ||
| 159 | |||
| 160 | Record record; | ||
| 161 | while ((record = view.Fetch()) != null) | ||
| 162 | { | ||
| 163 | var name = record.GetString(1); | ||
| 164 | result.Add(name); | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | result.Sort(); | ||
| 169 | return result.ToArray(); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs b/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs deleted file mode 100644 index 49d53351..00000000 --- a/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | public class RobocopyRunner : ExternalExecutable | ||
| 6 | { | ||
| 7 | private static readonly RobocopyRunner Instance = new RobocopyRunner(); | ||
| 8 | |||
| 9 | private RobocopyRunner() : base("robocopy") { } | ||
| 10 | |||
| 11 | public static ExternalExecutableResult Execute(string args) | ||
| 12 | { | ||
| 13 | return Instance.Run(args); | ||
| 14 | } | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/TestData.cs b/src/internal/WixBuildTools.TestSupport/TestData.cs deleted file mode 100644 index fc1ae4cc..00000000 --- a/src/internal/WixBuildTools.TestSupport/TestData.cs +++ /dev/null | |||
| @@ -1,78 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Reflection; | ||
| 8 | using System.Runtime.CompilerServices; | ||
| 9 | |||
| 10 | public class TestData | ||
| 11 | { | ||
| 12 | public static void CreateFile(string path, long size, bool fill = false) | ||
| 13 | { | ||
| 14 | // Ensure the directory exists. | ||
| 15 | path = Path.GetFullPath(path); | ||
| 16 | Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||
| 17 | |||
| 18 | using (var file = File.OpenWrite(path)) | ||
| 19 | { | ||
| 20 | if (fill) | ||
| 21 | { | ||
| 22 | var random = new Random(); | ||
| 23 | var bytes = new byte[4096]; | ||
| 24 | var generated = 0L; | ||
| 25 | |||
| 26 | // Put fill bytes in the file so it doesn't compress trivially. | ||
| 27 | while (generated < size) | ||
| 28 | { | ||
| 29 | var generate = (int)Math.Min(size - generated, bytes.Length); | ||
| 30 | |||
| 31 | random.NextBytes(bytes); | ||
| 32 | |||
| 33 | file.Write(bytes, 0, generate); | ||
| 34 | |||
| 35 | generated += generate; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | else | ||
| 39 | { | ||
| 40 | file.SetLength(size); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | public static string Get(params string[] paths) | ||
| 46 | { | ||
| 47 | var localPath = Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath); | ||
| 48 | return Path.Combine(localPath, Path.Combine(paths)); | ||
| 49 | } | ||
| 50 | |||
| 51 | public static string GetUnitTestLogsFolder([CallerFilePath] string path = "", [CallerMemberName] string method = "") | ||
| 52 | { | ||
| 53 | var startingPath = Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath); | ||
| 54 | var buildPath = startingPath; | ||
| 55 | |||
| 56 | while (!String.IsNullOrEmpty(buildPath)) | ||
| 57 | { | ||
| 58 | var folderName = Path.GetFileName(buildPath); | ||
| 59 | if (String.Equals("build", folderName, StringComparison.OrdinalIgnoreCase)) | ||
| 60 | { | ||
| 61 | break; | ||
| 62 | } | ||
| 63 | |||
| 64 | buildPath = Path.GetDirectoryName(buildPath); | ||
| 65 | } | ||
| 66 | |||
| 67 | if (String.IsNullOrEmpty(buildPath)) | ||
| 68 | { | ||
| 69 | throw new InvalidOperationException($"Could not find the 'build' folder in the test path: {startingPath}. Cannot get test logs folder without being able to find the build folder."); | ||
| 70 | } | ||
| 71 | |||
| 72 | var testLogsFolder = Path.Combine(buildPath, "logs", "UnitTests", $"{Path.GetFileNameWithoutExtension(path)}_{method}"); | ||
| 73 | Directory.CreateDirectory(testLogsFolder); | ||
| 74 | |||
| 75 | return testLogsFolder; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs deleted file mode 100644 index 15b7631d..00000000 --- a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs +++ /dev/null | |||
| @@ -1,42 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// This class builds on top of DisposableFileSystem | ||
| 9 | /// to make it easy to write a test that needs a whole folder of test data copied to a temp location | ||
| 10 | /// that will automatically be cleaned up at the end of the test. | ||
| 11 | /// </summary> | ||
| 12 | public class TestDataFolderFileSystem : IDisposable | ||
| 13 | { | ||
| 14 | private DisposableFileSystem fileSystem; | ||
| 15 | |||
| 16 | public string BaseFolder { get; private set; } | ||
| 17 | |||
| 18 | public void Dispose() | ||
| 19 | { | ||
| 20 | this.fileSystem?.Dispose(); | ||
| 21 | } | ||
| 22 | |||
| 23 | public void Initialize(string sourceDirectoryPath) | ||
| 24 | { | ||
| 25 | if (this.fileSystem != null) | ||
| 26 | { | ||
| 27 | throw new InvalidOperationException(); | ||
| 28 | } | ||
| 29 | this.fileSystem = new DisposableFileSystem(); | ||
| 30 | |||
| 31 | this.BaseFolder = this.fileSystem.GetFolder(); | ||
| 32 | |||
| 33 | RobocopyFolder(sourceDirectoryPath, this.BaseFolder); | ||
| 34 | } | ||
| 35 | |||
| 36 | public static ExternalExecutableResult RobocopyFolder(string sourceFolderPath, string destinationFolderPath) | ||
| 37 | { | ||
| 38 | var args = $"\"{sourceFolderPath}\" \"{destinationFolderPath}\" /E /R:1 /W:1"; | ||
| 39 | return RobocopyRunner.Execute(args); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs b/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs deleted file mode 100644 index 0197e125..00000000 --- a/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | |||
| 8 | public class VswhereRunner : ExternalExecutable | ||
| 9 | { | ||
| 10 | private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe"; | ||
| 11 | |||
| 12 | private static readonly object InitLock = new object(); | ||
| 13 | private static bool Initialized; | ||
| 14 | private static VswhereRunner Instance; | ||
| 15 | |||
| 16 | public static ExternalExecutableResult Execute(string args, bool mergeErrorIntoOutput = false) => | ||
| 17 | InitAndExecute(args, mergeErrorIntoOutput); | ||
| 18 | |||
| 19 | private static ExternalExecutableResult InitAndExecute(string args, bool mergeErrorIntoOutput) | ||
| 20 | { | ||
| 21 | lock (InitLock) | ||
| 22 | { | ||
| 23 | if (!Initialized) | ||
| 24 | { | ||
| 25 | Initialized = true; | ||
| 26 | var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath); | ||
| 27 | if (!File.Exists(vswherePath)) | ||
| 28 | { | ||
| 29 | throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}"); | ||
| 30 | } | ||
| 31 | |||
| 32 | Instance = new VswhereRunner(vswherePath); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | return Instance.Run(args, mergeErrorIntoOutput); | ||
| 37 | } | ||
| 38 | |||
| 39 | private VswhereRunner(string exePath) : base(exePath) { } | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj b/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj deleted file mode 100644 index 4e1c3c26..00000000 --- a/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | |||
| 6 | <PropertyGroup> | ||
| 7 | <TargetFrameworks>netstandard2.0;net472</TargetFrameworks> | ||
| 8 | <IsPackable>true</IsPackable> | ||
| 9 | <DebugType>embedded</DebugType> | ||
| 10 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 11 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 12 | <NoWarn>$(NoWarn);CS1591</NoWarn> | ||
| 13 | <SignOutput>false</SignOutput> | ||
| 14 | <IsWixTestSupportProject>true</IsWixTestSupportProject> | ||
| 15 | </PropertyGroup> | ||
| 16 | |||
| 17 | <ItemGroup> | ||
| 18 | <PackageReference Include="Microsoft.Build.Tasks.Core" /> | ||
| 19 | <PackageReference Include="WixToolset.Dtf.WindowsInstaller" /> | ||
| 20 | <PackageReference Include="WixToolset.Dtf.Compression" /> | ||
| 21 | <PackageReference Include="WixToolset.Dtf.Compression.Cab" /> | ||
| 22 | </ItemGroup> | ||
| 23 | |||
| 24 | <ItemGroup> | ||
| 25 | <PackageReference Include="xunit" /> | ||
| 26 | </ItemGroup> | ||
| 27 | </Project> | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkipTestException.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkipTestException.cs deleted file mode 100644 index bd7d23f9..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkipTestException.cs +++ /dev/null | |||
| @@ -1,15 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | |||
| 7 | public class SkipTestException : Exception | ||
| 8 | { | ||
| 9 | public SkipTestException(string reason) | ||
| 10 | : base(reason) | ||
| 11 | { | ||
| 12 | |||
| 13 | } | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactAttribute.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactAttribute.cs deleted file mode 100644 index 4974d489..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactAttribute.cs +++ /dev/null | |||
| @@ -1,13 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using Xunit; | ||
| 6 | using Xunit.Sdk; | ||
| 7 | |||
| 8 | // https://github.com/xunit/samples.xunit/blob/5dc1d35a63c3394a8678ac466b882576a70f56f6/DynamicSkipExample | ||
| 9 | [XunitTestCaseDiscoverer("WixBuildTools.TestSupport.XunitExtensions.SkippableFactDiscoverer", "WixBuildTools.TestSupport")] | ||
| 10 | public class SkippableFactAttribute : FactAttribute | ||
| 11 | { | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactDiscoverer.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactDiscoverer.cs deleted file mode 100644 index b692c912..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactDiscoverer.cs +++ /dev/null | |||
| @@ -1,23 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using Xunit.Abstractions; | ||
| 7 | using Xunit.Sdk; | ||
| 8 | |||
| 9 | public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer | ||
| 10 | { | ||
| 11 | private IMessageSink DiagnosticMessageSink { get; } | ||
| 12 | |||
| 13 | public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink) | ||
| 14 | { | ||
| 15 | this.DiagnosticMessageSink = diagnosticMessageSink; | ||
| 16 | } | ||
| 17 | |||
| 18 | public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) | ||
| 19 | { | ||
| 20 | yield return new SkippableFactTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); | ||
| 21 | } | ||
| 22 | } | ||
| 23 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactMessageBus.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactMessageBus.cs deleted file mode 100644 index 6d01889e..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactMessageBus.cs +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System.Linq; | ||
| 6 | using Xunit.Abstractions; | ||
| 7 | using Xunit.Sdk; | ||
| 8 | |||
| 9 | public class SkippableFactMessageBus : IMessageBus | ||
| 10 | { | ||
| 11 | private IMessageBus InnerBus { get; } | ||
| 12 | |||
| 13 | public SkippableFactMessageBus(IMessageBus innerBus) | ||
| 14 | { | ||
| 15 | this.InnerBus = innerBus; | ||
| 16 | } | ||
| 17 | |||
| 18 | public int DynamicallySkippedTestCount { get; private set; } | ||
| 19 | |||
| 20 | public void Dispose() | ||
| 21 | { | ||
| 22 | } | ||
| 23 | |||
| 24 | public bool QueueMessage(IMessageSinkMessage message) | ||
| 25 | { | ||
| 26 | if (message is ITestFailed testFailed) | ||
| 27 | { | ||
| 28 | var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); | ||
| 29 | if (exceptionType == typeof(SkipTestException).FullName) | ||
| 30 | { | ||
| 31 | ++this.DynamicallySkippedTestCount; | ||
| 32 | return this.InnerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | // Nothing we care about, send it on its way | ||
| 37 | return this.InnerBus.QueueMessage(message); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactTestCase.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactTestCase.cs deleted file mode 100644 index f13fec83..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactTestCase.cs +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.ComponentModel; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Threading.Tasks; | ||
| 9 | using Xunit.Abstractions; | ||
| 10 | using Xunit.Sdk; | ||
| 11 | |||
| 12 | public class SkippableFactTestCase : XunitTestCase | ||
| 13 | { | ||
| 14 | [EditorBrowsable(EditorBrowsableState.Never)] | ||
| 15 | [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] | ||
| 16 | public SkippableFactTestCase() { } | ||
| 17 | |||
| 18 | public SkippableFactTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) | ||
| 19 | : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) | ||
| 20 | { | ||
| 21 | } | ||
| 22 | |||
| 23 | public override async Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, | ||
| 24 | IMessageBus messageBus, | ||
| 25 | object[] constructorArguments, | ||
| 26 | ExceptionAggregator aggregator, | ||
| 27 | CancellationTokenSource cancellationTokenSource) | ||
| 28 | { | ||
| 29 | var skipMessageBus = new SkippableFactMessageBus(messageBus); | ||
| 30 | var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); | ||
| 31 | if (skipMessageBus.DynamicallySkippedTestCount > 0) | ||
| 32 | { | ||
| 33 | result.Failed -= skipMessageBus.DynamicallySkippedTestCount; | ||
| 34 | result.Skipped += skipMessageBus.DynamicallySkippedTestCount; | ||
| 35 | } | ||
| 36 | |||
| 37 | return result; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryAttribute.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryAttribute.cs deleted file mode 100644 index e026bb59..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryAttribute.cs +++ /dev/null | |||
| @@ -1,12 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using Xunit; | ||
| 6 | using Xunit.Sdk; | ||
| 7 | |||
| 8 | [XunitTestCaseDiscoverer("WixBuildTools.TestSupport.XunitExtensions.SkippableFactDiscoverer", "WixBuildTools.TestSupport")] | ||
| 9 | public class SkippableTheoryAttribute : TheoryAttribute | ||
| 10 | { | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryDiscoverer.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryDiscoverer.cs deleted file mode 100644 index cf4e2b43..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryDiscoverer.cs +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using Xunit.Abstractions; | ||
| 7 | using Xunit.Sdk; | ||
| 8 | |||
| 9 | public class SkippableTheoryDiscoverer : IXunitTestCaseDiscoverer | ||
| 10 | { | ||
| 11 | private IMessageSink DiagnosticMessageSink { get; } | ||
| 12 | private TheoryDiscoverer TheoryDiscoverer { get; } | ||
| 13 | |||
| 14 | public SkippableTheoryDiscoverer(IMessageSink diagnosticMessageSink) | ||
| 15 | { | ||
| 16 | this.DiagnosticMessageSink = diagnosticMessageSink; | ||
| 17 | |||
| 18 | this.TheoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink); | ||
| 19 | } | ||
| 20 | |||
| 21 | public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) | ||
| 22 | { | ||
| 23 | var defaultMethodDisplay = discoveryOptions.MethodDisplayOrDefault(); | ||
| 24 | var defaultMethodDisplayOptions = discoveryOptions.MethodDisplayOptionsOrDefault(); | ||
| 25 | |||
| 26 | // Unlike fact discovery, the underlying algorithm for theories is complex, so we let the theory discoverer | ||
| 27 | // do its work, and do a little on-the-fly conversion into our own test cases. | ||
| 28 | foreach (var testCase in this.TheoryDiscoverer.Discover(discoveryOptions, testMethod, factAttribute)) | ||
| 29 | { | ||
| 30 | if (testCase is XunitTheoryTestCase) | ||
| 31 | { | ||
| 32 | yield return new SkippableTheoryTestCase(this.DiagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testCase.TestMethod); | ||
| 33 | } | ||
| 34 | else | ||
| 35 | { | ||
| 36 | yield return new SkippableFactTestCase(this.DiagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testCase.TestMethod, testCase.TestMethodArguments); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryTestCase.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryTestCase.cs deleted file mode 100644 index 3299fe7e..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryTestCase.cs +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport.XunitExtensions | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.ComponentModel; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Threading.Tasks; | ||
| 9 | using Xunit.Abstractions; | ||
| 10 | using Xunit.Sdk; | ||
| 11 | |||
| 12 | public class SkippableTheoryTestCase : XunitTheoryTestCase | ||
| 13 | { | ||
| 14 | [EditorBrowsable(EditorBrowsableState.Never)] | ||
| 15 | [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] | ||
| 16 | public SkippableTheoryTestCase() { } | ||
| 17 | |||
| 18 | public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod) | ||
| 19 | : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) | ||
| 20 | { | ||
| 21 | } | ||
| 22 | |||
| 23 | public override async Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, | ||
| 24 | IMessageBus messageBus, | ||
| 25 | object[] constructorArguments, | ||
| 26 | ExceptionAggregator aggregator, | ||
| 27 | CancellationTokenSource cancellationTokenSource) | ||
| 28 | { | ||
| 29 | // Duplicated code from SkippableFactTestCase. I'm sure we could find a way to de-dup with some thought. | ||
| 30 | var skipMessageBus = new SkippableFactMessageBus(messageBus); | ||
| 31 | var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); | ||
| 32 | if (skipMessageBus.DynamicallySkippedTestCount > 0) | ||
| 33 | { | ||
| 34 | result.Failed -= skipMessageBus.DynamicallySkippedTestCount; | ||
| 35 | result.Skipped += skipMessageBus.DynamicallySkippedTestCount; | ||
| 36 | } | ||
| 37 | |||
| 38 | return result; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SpecificReturnCodeException.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SpecificReturnCodeException.cs deleted file mode 100644 index c703e90a..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SpecificReturnCodeException.cs +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using Xunit.Sdk; | ||
| 7 | |||
| 8 | public class SpecificReturnCodeException : XunitException | ||
| 9 | { | ||
| 10 | public SpecificReturnCodeException(int hrExpected, int hr, string userMessage) | ||
| 11 | : base(String.Format("WixAssert.SpecificReturnCode() Failure\r\n" + | ||
| 12 | "Expected HRESULT: 0x{0:X8}\r\n" + | ||
| 13 | "Actual HRESULT: 0x{1:X8}\r\n" + | ||
| 14 | "Message: {2}", | ||
| 15 | hrExpected, hr, userMessage)) | ||
| 16 | { | ||
| 17 | this.HResult = hr; | ||
| 18 | } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SucceededException.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/SucceededException.cs deleted file mode 100644 index 704fba28..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/SucceededException.cs +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using Xunit.Sdk; | ||
| 7 | |||
| 8 | public class SucceededException : XunitException | ||
| 9 | { | ||
| 10 | public SucceededException(int hr, string userMessage) | ||
| 11 | : base(String.Format("WixAssert.Succeeded() Failure\r\n" + | ||
| 12 | "HRESULT: 0x{0:X8}\r\n" + | ||
| 13 | "Message: {1}", | ||
| 14 | hr, userMessage)) | ||
| 15 | { | ||
| 16 | this.HResult = hr; | ||
| 17 | } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/internal/WixBuildTools.TestSupport/XunitExtensions/WixAssert.cs b/src/internal/WixBuildTools.TestSupport/XunitExtensions/WixAssert.cs deleted file mode 100644 index a8513bfb..00000000 --- a/src/internal/WixBuildTools.TestSupport/XunitExtensions/WixAssert.cs +++ /dev/null | |||
| @@ -1,201 +0,0 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixBuildTools.TestSupport | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml.Linq; | ||
| 10 | using WixBuildTools.TestSupport.XunitExtensions; | ||
| 11 | using Xunit; | ||
| 12 | using Xunit.Sdk; | ||
| 13 | |||
| 14 | public class WixAssert : Assert | ||
| 15 | { | ||
| 16 | public static void CompareLineByLine(string[] expectedLines, string[] actualLines) | ||
| 17 | { | ||
| 18 | var lineNumber = 0; | ||
| 19 | |||
| 20 | for (; lineNumber < expectedLines.Length && lineNumber < actualLines.Length; ++lineNumber) | ||
| 21 | { | ||
| 22 | WixAssert.StringEqual($"{lineNumber}: {expectedLines[lineNumber]}", $"{lineNumber}: {actualLines[lineNumber]}"); | ||
| 23 | } | ||
| 24 | |||
| 25 | var additionalExpectedLines = expectedLines.Length > lineNumber ? String.Join(Environment.NewLine, expectedLines.Skip(lineNumber).Select((s, i) => $"{lineNumber + i}: {s}")) : $"Missing {actualLines.Length - lineNumber} lines"; | ||
| 26 | var additionalActualLines = actualLines.Length > lineNumber ? String.Join(Environment.NewLine, actualLines.Skip(lineNumber).Select((s, i) => $"{lineNumber + i}: {s}")) : $"Missing {expectedLines.Length - lineNumber} lines"; | ||
| 27 | |||
| 28 | Assert.Equal<object>(additionalExpectedLines, additionalActualLines, StringObjectEqualityComparer.InvariantCulture); | ||
| 29 | } | ||
| 30 | |||
| 31 | public static void CompareXml(XContainer xExpected, XContainer xActual) | ||
| 32 | { | ||
| 33 | var expecteds = xExpected.Descendants().Select(x => $"{x.Name.LocalName}:{String.Join(",", x.Attributes().OrderBy(a => a.Name.LocalName).Select(a => $"{a.Name.LocalName}={a.Value}"))}"); | ||
| 34 | var actuals = xActual.Descendants().Select(x => $"{x.Name.LocalName}:{String.Join(",", x.Attributes().OrderBy(a => a.Name.LocalName).Select(a => $"{a.Name.LocalName}={a.Value}"))}"); | ||
| 35 | |||
| 36 | CompareLineByLine(expecteds.OrderBy(s => s).ToArray(), actuals.OrderBy(s => s).ToArray()); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static void CompareXml(string expectedPath, string actualPath) | ||
| 40 | { | ||
| 41 | var expectedDoc = XDocument.Load(expectedPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); | ||
| 42 | var actualDoc = XDocument.Load(actualPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); | ||
| 43 | |||
| 44 | CompareXml(expectedDoc, actualDoc); | ||
| 45 | } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Dynamically skips the test. | ||
| 49 | /// Requires that the test was marked with a fact attribute derived from <see cref="WixBuildTools.TestSupport.XunitExtensions.SkippableFactAttribute" /> | ||
| 50 | /// or <see cref="WixBuildTools.TestSupport.XunitExtensions.SkippableTheoryAttribute" /> | ||
| 51 | /// </summary> | ||
| 52 | public static void Skip(string message) | ||
| 53 | { | ||
| 54 | throw new SkipTestException(message); | ||
| 55 | } | ||
| 56 | |||
| 57 | public static void SpecificReturnCode(int hrExpected, int hr, string format, params object[] formatArgs) | ||
| 58 | { | ||
| 59 | if (hrExpected != hr) | ||
| 60 | { | ||
| 61 | throw new SpecificReturnCodeException(hrExpected, hr, String.Format(format, formatArgs)); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | public static void Succeeded(int hr, string format, params object[] formatArgs) | ||
| 66 | { | ||
| 67 | if (0 > hr) | ||
| 68 | { | ||
| 69 | throw new SucceededException(hr, String.Format(format, formatArgs)); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | public static void StringCollectionEmpty(IList<string> collection) | ||
| 74 | { | ||
| 75 | if (collection.Count > 0) | ||
| 76 | { | ||
| 77 | Assert.True(false, $"The collection was expected to be empty, but instead was [{Environment.NewLine}\"{String.Join($"\", {Environment.NewLine}\"", collection)}\"{Environment.NewLine}]"); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | public static void StringEqual(string expected, string actual, bool ignoreCase = false) | ||
| 82 | { | ||
| 83 | WixStringEqualException.ThrowIfNotEqual(expected, actual, ignoreCase); | ||
| 84 | } | ||
| 85 | |||
| 86 | public static void NotStringEqual(string expected, string actual, bool ignoreCase = false) | ||
| 87 | { | ||
| 88 | var comparer = ignoreCase ? StringObjectEqualityComparer.InvariantCultureIgnoreCase : StringObjectEqualityComparer.InvariantCulture; | ||
| 89 | Assert.NotEqual<object>(expected, actual, comparer); | ||
| 90 | } | ||
| 91 | |||
| 92 | // There appears to have been a bug in VC++, which might or might not have been partially | ||
| 93 | // or completely corrected. It was unable to disambiguate a call to: | ||
| 94 | // Xunit::Assert::Throws(System::Type^, System::Action^) | ||
| 95 | // from a call to: | ||
| 96 | // Xunit::Assert::Throws(System::Type^, System::Func<System::Object^>^) | ||
| 97 | // that implicitly ignores its return value. | ||
| 98 | // | ||
| 99 | // The ambiguity may have been reported by some versions of the compiler and not by others. | ||
| 100 | // Some versions of the compiler may not have emitted any code in this situation, making it | ||
| 101 | // appear that the test has passed when, in fact, it hasn't been run. | ||
| 102 | // | ||
| 103 | // This situation is not an issue for C#. | ||
| 104 | // | ||
| 105 | // The following method is used to isolate DUtilTests in order to overcome the above problem. | ||
| 106 | |||
| 107 | /// <summary> | ||
| 108 | /// This shim allows C++/CLR code to call the Xunit method with the same signature | ||
| 109 | /// without getting an ambiguous overload error. If the specified test code | ||
| 110 | /// fails to generate an exception of the exact specified type, an assertion | ||
| 111 | /// exception is thrown. Otherwise, execution flow proceeds as normal. | ||
| 112 | /// </summary> | ||
| 113 | /// <typeparam name="T">The type name of the expected exception.</typeparam> | ||
| 114 | /// <param name="testCode">An Action delegate to run the test code.</param> | ||
| 115 | public static new void Throws<T>(System.Action testCode) | ||
| 116 | where T : System.Exception | ||
| 117 | { | ||
| 118 | Xunit.Assert.Throws<T>(testCode); | ||
| 119 | } | ||
| 120 | |||
| 121 | // This shim has been tested, but is not currently used anywhere. It was provided | ||
| 122 | // at the same time as the preceding shim because it involved the same overload | ||
| 123 | // resolution conflict. | ||
| 124 | |||
| 125 | /// <summary> | ||
| 126 | /// This shim allows C++/CLR code to call the Xunit method with the same signature | ||
| 127 | /// without getting an ambiguous overload error. If the specified test code | ||
| 128 | /// fails to generate an exception of the exact specified type, an assertion | ||
| 129 | /// exception is thrown. Otherwise, execution flow proceeds as normal. | ||
| 130 | /// </summary> | ||
| 131 | /// <param name="exceptionType">The type object associated with exceptions of the expected type.</param> | ||
| 132 | /// <param name="testCode">An Action delegate to run the test code.</param> | ||
| 133 | /// <returns>An exception of a type other than the type specified, is such an exception is thrown.</returns> | ||
| 134 | public static new System.Exception Throws(System.Type exceptionType, System.Action testCode) | ||
| 135 | { | ||
| 136 | return Xunit.Assert.Throws(exceptionType, testCode); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | internal class StringObjectEqualityComparer : IEqualityComparer<object> | ||
| 141 | { | ||
| 142 | public static readonly StringObjectEqualityComparer InvariantCultureIgnoreCase = new StringObjectEqualityComparer(true); | ||
| 143 | public static readonly StringObjectEqualityComparer InvariantCulture = new StringObjectEqualityComparer(false); | ||
| 144 | |||
| 145 | private readonly StringComparer stringComparer; | ||
| 146 | |||
| 147 | public StringObjectEqualityComparer(bool ignoreCase) | ||
| 148 | { | ||
| 149 | this.stringComparer = ignoreCase ? StringComparer.InvariantCultureIgnoreCase : StringComparer.InvariantCulture; | ||
| 150 | } | ||
| 151 | |||
| 152 | public new bool Equals(object x, object y) | ||
| 153 | { | ||
| 154 | return this.stringComparer.Equals((string)x, (string)y); | ||
| 155 | } | ||
| 156 | |||
| 157 | public int GetHashCode(object obj) | ||
| 158 | { | ||
| 159 | return this.stringComparer.GetHashCode((string)obj); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | public class WixStringEqualException : XunitException | ||
| 164 | { | ||
| 165 | public WixStringEqualException(string userMessage) : base(userMessage) { } | ||
| 166 | |||
| 167 | public static void ThrowIfNotEqual(string expected, string actual, bool ignoreCase) | ||
| 168 | { | ||
| 169 | var comparer = ignoreCase ? StringObjectEqualityComparer.InvariantCultureIgnoreCase : StringObjectEqualityComparer.InvariantCulture; | ||
| 170 | if (comparer.Equals(expected, actual)) | ||
| 171 | { | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | |||
| 175 | var sbMessage = new StringBuilder(); | ||
| 176 | |||
| 177 | try | ||
| 178 | { | ||
| 179 | Assert.Equal(expected, actual, ignoreCase); | ||
| 180 | } | ||
| 181 | catch (XunitException xe) | ||
| 182 | { | ||
| 183 | // If either string is not completely in the message, then make sure it gets in there. | ||
| 184 | if (!xe.Message.Contains(expected) || !xe.Message.Contains(actual)) | ||
| 185 | { | ||
| 186 | sbMessage.AppendLine(xe.Message); | ||
| 187 | sbMessage.AppendLine(); | ||
| 188 | sbMessage.AppendFormat("Expected: {0}", expected); | ||
| 189 | sbMessage.AppendLine(); | ||
| 190 | sbMessage.AppendFormat("Actual: {0}", actual); | ||
| 191 | } | ||
| 192 | else | ||
| 193 | { | ||
| 194 | throw; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | throw new WixStringEqualException(sbMessage.ToString()); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
