aboutsummaryrefslogtreecommitdiff
path: root/src/internal/WixBuildTools.TestSupport
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-11-08 14:58:05 -0800
committerRob Mensching <rob@firegiant.com>2022-11-08 16:20:25 -0800
commitc843b47d6233153fa961c6d0e61edf7cedf255bb (patch)
tree9eae6badd42d3badb8665b7414b4d44ca48d6ae1 /src/internal/WixBuildTools.TestSupport
parent7e498d6348c26583972ea1cdf7d51dadc8f5b792 (diff)
downloadwix-c843b47d6233153fa961c6d0e61edf7cedf255bb.tar.gz
wix-c843b47d6233153fa961c6d0e61edf7cedf255bb.tar.bz2
wix-c843b47d6233153fa961c6d0e61edf7cedf255bb.zip
Separate WixInternal content from official WixToolset namespace
Diffstat (limited to 'src/internal/WixBuildTools.TestSupport')
-rw-r--r--src/internal/WixBuildTools.TestSupport/Builder.cs158
-rw-r--r--src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs93
-rw-r--r--src/internal/WixBuildTools.TestSupport/DotnetRunner.cs57
-rw-r--r--src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs374
-rw-r--r--src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs19
-rw-r--r--src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs33
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs112
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs19
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildUtilities.cs101
-rw-r--r--src/internal/WixBuildTools.TestSupport/Pushd.cs46
-rw-r--r--src/internal/WixBuildTools.TestSupport/Query.cs172
-rw-r--r--src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs16
-rw-r--r--src/internal/WixBuildTools.TestSupport/TestData.cs78
-rw-r--r--src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs42
-rw-r--r--src/internal/WixBuildTools.TestSupport/VswhereRunner.cs41
-rw-r--r--src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj27
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkipTestException.cs15
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactAttribute.cs13
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactDiscoverer.cs23
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactMessageBus.cs40
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableFactTestCase.cs40
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryAttribute.cs12
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryDiscoverer.cs41
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SkippableTheoryTestCase.cs41
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SpecificReturnCodeException.cs20
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/SucceededException.cs19
-rw-r--r--src/internal/WixBuildTools.TestSupport/XunitExtensions/WixAssert.cs201
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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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}