aboutsummaryrefslogtreecommitdiff
path: root/src/internal/WixBuildTools.TestSupport
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal/WixBuildTools.TestSupport')
-rw-r--r--src/internal/WixBuildTools.TestSupport/Builder.cs70
-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.cs88
-rw-r--r--src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs17
-rw-r--r--src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs33
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs168
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs19
-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/SucceededException.cs18
-rw-r--r--src/internal/WixBuildTools.TestSupport/TestData.cs16
-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/WixAssert.cs47
-rw-r--r--src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj31
17 files changed, 974 insertions, 0 deletions
diff --git a/src/internal/WixBuildTools.TestSupport/Builder.cs b/src/internal/WixBuildTools.TestSupport/Builder.cs
new file mode 100644
index 00000000..ef0de8c9
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Builder.cs
@@ -0,0 +1,70 @@
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}
diff --git a/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs b/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs
new file mode 100644
index 00000000..f096db72
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs
@@ -0,0 +1,93 @@
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
new file mode 100644
index 00000000..82391178
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs
@@ -0,0 +1,57 @@
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
new file mode 100644
index 00000000..eb07aa13
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs
@@ -0,0 +1,88 @@
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.Concurrent;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Text;
10
11 public abstract class ExternalExecutable
12 {
13 private readonly string exePath;
14
15 protected ExternalExecutable(string exePath)
16 {
17 this.exePath = exePath;
18 }
19
20 protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null)
21 {
22 var startInfo = new ProcessStartInfo(this.exePath, args)
23 {
24 CreateNoWindow = true,
25 RedirectStandardError = true,
26 RedirectStandardOutput = true,
27 UseShellExecute = false,
28 WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath),
29 };
30
31 using (var process = Process.Start(startInfo))
32 {
33 // This implementation of merging the streams does not guarantee that lines are retrieved in the same order that they were written.
34 // If the process is simultaneously writing to both streams, this is impossible to do anyway.
35 var standardOutput = new ConcurrentQueue<string>();
36 var standardError = mergeErrorIntoOutput ? standardOutput : new ConcurrentQueue<string>();
37
38 process.ErrorDataReceived += (s, e) => { if (e.Data != null) { standardError.Enqueue(e.Data); } };
39 process.OutputDataReceived += (s, e) => { if (e.Data != null) { standardOutput.Enqueue(e.Data); } };
40
41 process.BeginErrorReadLine();
42 process.BeginOutputReadLine();
43
44 process.WaitForExit();
45
46 return new ExternalExecutableResult
47 {
48 ExitCode = process.ExitCode,
49 StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(),
50 StandardOutput = standardOutput.ToArray(),
51 StartInfo = startInfo,
52 };
53 }
54 }
55
56 // This is internal because it assumes backslashes aren't used as escape characters and there aren't any double quotes.
57 internal static string CombineArguments(IEnumerable<string> arguments)
58 {
59 if (arguments == null)
60 {
61 return null;
62 }
63
64 var sb = new StringBuilder();
65
66 foreach (var arg in arguments)
67 {
68 if (sb.Length > 0)
69 {
70 sb.Append(' ');
71 }
72
73 if (arg.IndexOf(' ') > -1)
74 {
75 sb.Append("\"");
76 sb.Append(arg);
77 sb.Append("\"");
78 }
79 else
80 {
81 sb.Append(arg);
82 }
83 }
84
85 return sb.ToString();
86 }
87 }
88}
diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs
new file mode 100644
index 00000000..19b5183b
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs
@@ -0,0 +1,17 @@
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 ProcessStartInfo StartInfo { get; set; }
16 }
17}
diff --git a/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs b/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs
new file mode 100644
index 00000000..20545970
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs
@@ -0,0 +1,33 @@
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
new file mode 100644
index 00000000..35e53de6
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs
@@ -0,0 +1,168 @@
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";
12 private static readonly string Msbuild15RelativePath = @"MSBuild\15.0\Bin\MSBuild.exe";
13 private static readonly string Msbuild15RelativePath64 = @"MSBuild\15.0\Bin\amd64\MSBuild.exe";
14 private static readonly string MsbuildCurrentRelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
15 private static readonly string MsbuildCurrentRelativePath64 = @"MSBuild\Current\Bin\amd64\MSBuild.exe";
16
17 private static readonly object InitLock = new object();
18
19 private static bool Initialized;
20 private static MsbuildRunner Msbuild15Runner;
21 private static MsbuildRunner Msbuild15Runner64;
22 private static MsbuildRunner MsbuildCurrentRunner;
23 private static MsbuildRunner MsbuildCurrentRunner64;
24
25 public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null, bool x64 = false) =>
26 InitAndExecute(String.Empty, projectPath, arguments, x64);
27
28 public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null, bool x64 = false) =>
29 InitAndExecute("15", projectPath, arguments, x64);
30
31 public static MsbuildRunnerResult ExecuteWithMsbuildCurrent(string projectPath, string[] arguments = null, bool x64 = false) =>
32 InitAndExecute("Current", projectPath, arguments, x64);
33
34 private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments, bool x64)
35 {
36 lock (InitLock)
37 {
38 if (!Initialized)
39 {
40 Initialized = true;
41 var vswhereResult = VswhereRunner.Execute(VswhereFindArguments, true);
42 if (vswhereResult.ExitCode != 0)
43 {
44 throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {vswhereResult.ExitCode}. Output:\r\n{String.Join("\r\n", vswhereResult.StandardOutput)}");
45 }
46
47 string msbuild15Path = null;
48 string msbuild15Path64 = null;
49 string msbuildCurrentPath = null;
50 string msbuildCurrentPath64 = null;
51
52 foreach (var installPath in vswhereResult.StandardOutput)
53 {
54 if (msbuildCurrentPath == null)
55 {
56 var path = Path.Combine(installPath, MsbuildCurrentRelativePath);
57 if (File.Exists(path))
58 {
59 msbuildCurrentPath = path;
60 }
61 }
62
63 if (msbuildCurrentPath64 == null)
64 {
65 var path = Path.Combine(installPath, MsbuildCurrentRelativePath64);
66 if (File.Exists(path))
67 {
68 msbuildCurrentPath64 = path;
69 }
70 }
71
72 if (msbuild15Path == null)
73 {
74 var path = Path.Combine(installPath, Msbuild15RelativePath);
75 if (File.Exists(path))
76 {
77 msbuild15Path = path;
78 }
79 }
80
81 if (msbuild15Path64 == null)
82 {
83 var path = Path.Combine(installPath, Msbuild15RelativePath64);
84 if (File.Exists(path))
85 {
86 msbuild15Path64 = path;
87 }
88 }
89 }
90
91 if (msbuildCurrentPath != null)
92 {
93 MsbuildCurrentRunner = new MsbuildRunner(msbuildCurrentPath);
94 }
95
96 if (msbuildCurrentPath64 != null)
97 {
98 MsbuildCurrentRunner64 = new MsbuildRunner(msbuildCurrentPath64);
99 }
100
101 if (msbuild15Path != null)
102 {
103 Msbuild15Runner = new MsbuildRunner(msbuild15Path);
104 }
105
106 if (msbuild15Path64 != null)
107 {
108 Msbuild15Runner64 = new MsbuildRunner(msbuild15Path64);
109 }
110 }
111 }
112
113 MsbuildRunner runner;
114 switch (msbuildVersion)
115 {
116 case "15":
117 {
118 runner = x64 ? Msbuild15Runner64 : Msbuild15Runner;
119 break;
120 }
121 case "Current":
122 {
123 runner = x64 ? MsbuildCurrentRunner64 : MsbuildCurrentRunner;
124 break;
125 }
126 default:
127 {
128 runner = x64 ? MsbuildCurrentRunner64 ?? Msbuild15Runner64
129 : MsbuildCurrentRunner ?? Msbuild15Runner;
130 break;
131 }
132 }
133
134 if (runner == null)
135 {
136 throw new InvalidOperationException($"Failed to find an installed{(x64 ? " 64-bit" : String.Empty)} MSBuild{msbuildVersion}");
137 }
138
139 return runner.ExecuteCore(projectPath, arguments);
140 }
141
142 private MsbuildRunner(string exePath) : base(exePath) { }
143
144 private MsbuildRunnerResult ExecuteCore(string projectPath, string[] arguments)
145 {
146 var total = new List<string>
147 {
148 projectPath,
149 };
150
151 if (arguments != null)
152 {
153 total.AddRange(arguments);
154 }
155
156 var args = CombineArguments(total);
157 var mergeErrorIntoOutput = true;
158 var workingFolder = Path.GetDirectoryName(projectPath);
159 var result = this.Run(args, mergeErrorIntoOutput, workingFolder);
160
161 return new MsbuildRunnerResult
162 {
163 ExitCode = result.ExitCode,
164 Output = result.StandardOutput,
165 };
166 }
167 }
168}
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs b/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs
new file mode 100644
index 00000000..5610987e
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs
@@ -0,0 +1,19 @@
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:\r\n{String.Join("\r\n", this.Output)}");
17 }
18 }
19}
diff --git a/src/internal/WixBuildTools.TestSupport/Pushd.cs b/src/internal/WixBuildTools.TestSupport/Pushd.cs
new file mode 100644
index 00000000..d0545215
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Pushd.cs
@@ -0,0 +1,46 @@
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
new file mode 100644
index 00000000..101a8890
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Query.cs
@@ -0,0 +1,172 @@
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
new file mode 100644
index 00000000..49d53351
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs
@@ -0,0 +1,16 @@
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/SucceededException.cs b/src/internal/WixBuildTools.TestSupport/SucceededException.cs
new file mode 100644
index 00000000..00b31d68
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/SucceededException.cs
@@ -0,0 +1,18 @@
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 }
17 }
18}
diff --git a/src/internal/WixBuildTools.TestSupport/TestData.cs b/src/internal/WixBuildTools.TestSupport/TestData.cs
new file mode 100644
index 00000000..8587330d
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/TestData.cs
@@ -0,0 +1,16 @@
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 TestData
9 {
10 public static string Get(params string[] paths)
11 {
12 var localPath = Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetCallingAssembly().CodeBase).LocalPath);
13 return Path.Combine(localPath, Path.Combine(paths));
14 }
15 }
16}
diff --git a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs
new file mode 100644
index 00000000..8d670bf0
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs
@@ -0,0 +1,42 @@
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 private 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
new file mode 100644
index 00000000..0197e125
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs
@@ -0,0 +1,41 @@
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/WixAssert.cs b/src/internal/WixBuildTools.TestSupport/WixAssert.cs
new file mode 100644
index 00000000..5638a787
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/WixAssert.cs
@@ -0,0 +1,47 @@
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.Linq;
7 using System.Xml.Linq;
8 using Xunit;
9
10 public class WixAssert : Assert
11 {
12 public static void CompareLineByLine(string[] expectedLines, string[] actualLines)
13 {
14 for (var i = 0; i < expectedLines.Length; ++i)
15 {
16 Assert.True(actualLines.Length > i, $"{i}: expectedLines longer than actualLines");
17 Assert.Equal($"{i}: {expectedLines[i]}", $"{i}: {actualLines[i]}");
18 }
19
20 Assert.True(expectedLines.Length == actualLines.Length, "actualLines longer than expectedLines");
21 }
22
23 public static void CompareXml(XContainer xExpected, XContainer xActual)
24 {
25 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}"))}");
26 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}"))}");
27
28 CompareLineByLine(expecteds.OrderBy(s => s).ToArray(), actuals.OrderBy(s => s).ToArray());
29 }
30
31 public static void CompareXml(string expectedPath, string actualPath)
32 {
33 var expectedDoc = XDocument.Load(expectedPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
34 var actualDoc = XDocument.Load(actualPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
35
36 CompareXml(expectedDoc, actualDoc);
37 }
38
39 public static void Succeeded(int hr, string format, params object[] formatArgs)
40 {
41 if (0 > hr)
42 {
43 throw new SucceededException(hr, String.Format(format, formatArgs));
44 }
45 }
46 }
47}
diff --git a/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj b/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
new file mode 100644
index 00000000..f59e5eca
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
@@ -0,0 +1,31 @@
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;net461;net472</TargetFrameworks>
8 <IsPackable>true</IsPackable>
9 <DebugType>embedded</DebugType>
10 <PublishRepositoryUrl>true</PublishRepositoryUrl>
11 <CreateDocumentationFile>true</CreateDocumentationFile>
12 <NoWarn>CS1591</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <PackageReference Include="Microsoft.Build.Tasks.Core" Version="14.3" />
17 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" />
18 <PackageReference Include="WixToolset.Dtf.Compression" Version="4.0.*" />
19 <PackageReference Include="WixToolset.Dtf.Compression.Cab" Version="4.0.*" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26
27 <ItemGroup>
28 <PackageReference Include="xunit.assert" Version="2.4.1" />
29 </ItemGroup>
30
31</Project>