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