diff --git a/ICSharpCode.Decompiler/Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler/Tests/CorrectnessTestRunner.cs
index 4b1c4fb73..24f40be17 100644
--- a/ICSharpCode.Decompiler/Tests/CorrectnessTestRunner.cs
+++ b/ICSharpCode.Decompiler/Tests/CorrectnessTestRunner.cs
@@ -1,10 +1,25 @@
-using System;
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
using System.CodeDom.Compiler;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using ICSharpCode.Decompiler.Tests.Helpers;
using NUnit.Framework;
diff --git a/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs b/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs
index 3e5a42ff4..c6f6d9c1a 100644
--- a/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs
+++ b/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs
@@ -10,6 +10,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
{
public class CodeAssert
{
+ public static void FilesAreEqual(string fileName1, string fileName2)
+ {
+ AreEqual(File.ReadAllText(fileName1), File.ReadAllText(fileName2));
+ }
+
public static void AreEqual(string input1, string input2)
{
var diff = new StringWriter();
diff --git a/ICSharpCode.Decompiler/Tests/Helpers/SdkUtility.cs b/ICSharpCode.Decompiler/Tests/Helpers/SdkUtility.cs
new file mode 100644
index 000000000..a240a9222
--- /dev/null
+++ b/ICSharpCode.Decompiler/Tests/Helpers/SdkUtility.cs
@@ -0,0 +1,206 @@
+// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Linq;
+using Microsoft.Win32;
+
+namespace ICSharpCode.Decompiler.Tests.Helpers
+{
+ public static class SdkUtility
+ {
+ static string GetPathFromRegistry(string key, string valueName)
+ {
+ using (RegistryKey installRootKey = Registry.LocalMachine.OpenSubKey(key)) {
+ if (installRootKey != null) {
+ object o = installRootKey.GetValue(valueName);
+ if (o != null) {
+ string r = o.ToString();
+ if (!string.IsNullOrEmpty(r))
+ return r;
+ }
+ }
+ }
+ return null;
+ }
+
+ static string GetPathFromRegistryX86(string key, string valueName)
+ {
+ using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) {
+ using (RegistryKey installRootKey = baseKey.OpenSubKey(key)) {
+ if (installRootKey != null) {
+ object o = installRootKey.GetValue(valueName);
+ if (o != null) {
+ string r = o.ToString();
+ if (!string.IsNullOrEmpty(r))
+ return r;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ #region InstallRoot Properties
+
+ static string netFrameworkInstallRoot = null;
+ ///
+ /// Gets the installation root of the .NET Framework (@"C:\Windows\Microsoft.NET\Framework\")
+ ///
+ public static string NetFrameworkInstallRoot {
+ get {
+ if (netFrameworkInstallRoot == null) {
+ netFrameworkInstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\.NETFramework", "InstallRoot") ?? string.Empty;
+ }
+ return netFrameworkInstallRoot;
+ }
+ }
+
+ static string netSdk20InstallRoot = null;
+ ///
+ /// Location of the .NET 2.0 SDK install root.
+ ///
+ public static string NetSdk20InstallRoot {
+ get {
+ if (netSdk20InstallRoot == null) {
+ netSdk20InstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\.NETFramework", "sdkInstallRootv2.0") ?? string.Empty;
+ }
+ return netSdk20InstallRoot;
+ }
+ }
+
+ static string windowsSdk60InstallRoot = null;
+ ///
+ /// Location of the .NET 3.0 SDK (Windows SDK 6.0) install root.
+ ///
+ public static string WindowsSdk60InstallRoot {
+ get {
+ if (windowsSdk60InstallRoot == null) {
+ windowsSdk60InstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk60InstallRoot;
+ }
+ }
+
+ static string windowsSdk60aInstallRoot = null;
+ ///
+ /// Location of the Windows SDK Components in Visual Studio 2008 (.NET 3.5; Windows SDK 6.0a).
+ ///
+ public static string WindowsSdk60aInstallRoot {
+ get {
+ if (windowsSdk60aInstallRoot == null) {
+ windowsSdk60aInstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0a", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk60aInstallRoot;
+ }
+ }
+
+ static string windowsSdk61InstallRoot = null;
+ ///
+ /// Location of the .NET 3.5 SDK (Windows SDK 6.1) install root.
+ ///
+ public static string WindowsSdk61InstallRoot {
+ get {
+ if (windowsSdk61InstallRoot == null) {
+ windowsSdk61InstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk61InstallRoot;
+ }
+ }
+
+ static string windowsSdk70InstallRoot = null;
+ ///
+ /// Location of the .NET 3.5 SP1 SDK (Windows SDK 7.0) install root.
+ ///
+ public static string WindowsSdk70InstallRoot {
+ get {
+ if (windowsSdk70InstallRoot == null) {
+ windowsSdk70InstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk70InstallRoot;
+ }
+ }
+
+ static string windowsSdk71InstallRoot = null;
+ ///
+ /// Location of the .NET 4.0 SDK (Windows SDK 7.1) install root.
+ ///
+ public static string WindowsSdk71InstallRoot {
+ get {
+ if (windowsSdk71InstallRoot == null) {
+ windowsSdk71InstallRoot = GetPathFromRegistry(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk71InstallRoot;
+ }
+ }
+
+ static string windowsSdk80InstallRoot = null;
+ ///
+ /// Location of the .NET 4.5 SDK (Windows SDK 8.0) install root.
+ ///
+ public static string WindowsSdk80NetFxTools {
+ get {
+ if (windowsSdk80InstallRoot == null) {
+ windowsSdk80InstallRoot = GetPathFromRegistryX86(@"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0A\WinSDK-NetFx40Tools", "InstallationFolder") ?? string.Empty;
+ }
+ return windowsSdk80InstallRoot;
+ }
+ }
+ #endregion
+
+ ///
+ /// Searches all the .net sdk bin folders and return the path of the
+ /// exe from the latest sdk.
+ ///
+ /// The EXE to search for.
+ /// The path of the executable, or null if the exe is not found.
+ public static string GetSdkPath(string exeName) {
+ string execPath;
+ if (!string.IsNullOrEmpty(WindowsSdk80NetFxTools)) {
+ execPath = Path.Combine(WindowsSdk80NetFxTools, exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(WindowsSdk71InstallRoot)) {
+ execPath = Path.Combine(WindowsSdk71InstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(WindowsSdk70InstallRoot)) {
+ execPath = Path.Combine(WindowsSdk70InstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(WindowsSdk61InstallRoot)) {
+ execPath = Path.Combine(WindowsSdk61InstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(WindowsSdk60aInstallRoot)) {
+ execPath = Path.Combine(WindowsSdk60aInstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(WindowsSdk60InstallRoot)) {
+ execPath = Path.Combine(WindowsSdk60InstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ if (!string.IsNullOrEmpty(NetSdk20InstallRoot)) {
+ execPath = Path.Combine(NetSdk20InstallRoot, "bin\\" + exeName);
+ if (File.Exists(execPath)) { return execPath; }
+ }
+ return null;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs
index f02662c9d..98e67c282 100644
--- a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs
+++ b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs
@@ -87,6 +87,30 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return outputFile;
}
+ public static string Disassemble(string sourceFileName, string outputFile)
+ {
+ string ildasmPath = SdkUtility.GetSdkPath("ildasm.exe");
+
+ ProcessStartInfo info = new ProcessStartInfo(ildasmPath);
+ info.Arguments = $"/out=\"{outputFile}\" \"{sourceFileName}\"";
+ info.RedirectStandardError = true;
+ info.RedirectStandardOutput = true;
+ info.UseShellExecute = false;
+
+ Process process = Process.Start(info);
+
+ var outputTask = process.StandardOutput.ReadToEndAsync();
+ var errorTask = process.StandardError.ReadToEndAsync();
+
+ Task.WaitAll(outputTask, errorTask);
+ process.WaitForExit();
+
+ Console.WriteLine("output: " + outputTask.Result);
+ Console.WriteLine("errors: " + errorTask.Result);
+
+ return outputFile;
+ }
+
public static CompilerResults CompileCSharp(string sourceFileName, CompilerOptions flags = CompilerOptions.UseDebug)
{
List sourceFileNames = new List { sourceFileName };
diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
index a5ba198ae..c71476add 100644
--- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -112,10 +112,12 @@
+
+
@@ -132,6 +134,7 @@
+
@@ -145,6 +148,9 @@
+
+ HelloWorld.cs
+
diff --git a/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs
new file mode 100644
index 000000000..8ad94ec82
--- /dev/null
+++ b/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs
@@ -0,0 +1,84 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using ICSharpCode.Decompiler.Tests.Helpers;
+using NUnit.Framework;
+
+namespace ICSharpCode.Decompiler.Tests
+{
+ public class PrettyTestRunner
+ {
+ const string TestCasePath = @"../../Tests/TestCases/Pretty";
+
+ [Test]
+ public void AllFilesHaveTests()
+ {
+ var testNames = typeof(PrettyTestRunner).GetMethods()
+ .Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Any())
+ .Select(m => m.Name)
+ .ToArray();
+ foreach (var file in new DirectoryInfo(TestCasePath).EnumerateFiles()) {
+ if (file.Extension == ".txt" || file.Extension == ".exe")
+ continue;
+ var testName = Path.GetFileNameWithoutExtension(file.Name);
+ Assert.Contains(testName, testNames);
+ }
+ }
+
+ [Test]
+ public void HelloWorld()
+ {
+ Run("HelloWorld");
+ Run("HelloWorld", AssemblerOptions.UseDebug);
+ }
+
+ void Run(string testName, AssemblerOptions asmOptions = AssemblerOptions.None)
+ {
+ var ilFile = Path.Combine(TestCasePath, testName + ".il");
+ var csFile = Path.Combine(TestCasePath, testName + ".cs");
+ EnsureSourceFilesExist(Path.Combine(TestCasePath, testName));
+
+ var executable = Tester.AssembleIL(ilFile, asmOptions);
+ var decompiled = Tester.DecompileCSharp(executable);
+
+ CodeAssert.FilesAreEqual(csFile, decompiled);
+ }
+
+ void EnsureSourceFilesExist(string fileName)
+ {
+ if (!File.Exists(fileName + ".il")) {
+ CompilerResults output = null;
+ try {
+ output = Tester.CompileCSharp(fileName + ".cs", CompilerOptions.None);
+ Tester.Disassemble(output.PathToAssembly, fileName + ".il");
+ } finally {
+ if (output != null)
+ output.TempFiles.Delete();
+ }
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Pretty/HelloWorld.cs b/ICSharpCode.Decompiler/Tests/TestCases/Pretty/HelloWorld.cs
new file mode 100644
index 000000000..6876965e9
--- /dev/null
+++ b/ICSharpCode.Decompiler/Tests/TestCases/Pretty/HelloWorld.cs
@@ -0,0 +1,30 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace Pretty.HelloWorld
+{
+ public class HelloWorld
+ {
+ public static void Main()
+ {
+ Console.WriteLine("Hello World!");
+ }
+ }
+}
\ No newline at end of file