diff --git a/Confuser.Core/ConfuserEngine.cs b/Confuser.Core/ConfuserEngine.cs index 91594f9cf..51933f503 100644 --- a/Confuser.Core/ConfuserEngine.cs +++ b/Confuser.Core/ConfuserEngine.cs @@ -462,6 +462,49 @@ static void SaveModules(ConfuserContext context) { context.Logger.LogDebug("Saving to '{0}'...", path); File.WriteAllBytes(path, context.OutputModules[i]); } + + if (context.Project.CopyRuntimeFiles) + CopyRuntimeFiles(context); + } + + static void CopyRuntimeFiles(ConfuserContext context) { + for (int i = 0; i < context.OutputModules.Count; i++) { + string sourcePath = context.Modules[i].Location; + if (string.IsNullOrEmpty(sourcePath)) + continue; + if (!Path.IsPathRooted(sourcePath)) + sourcePath = Path.Combine(context.BaseDirectory, sourcePath); + + string sourceDir = Path.GetDirectoryName(sourcePath); + string baseName = Path.GetFileNameWithoutExtension(sourcePath); + if (sourceDir is null || string.IsNullOrEmpty(baseName)) + continue; + + // A .runtimeconfig.json sibling marks a .NET (Core) app, whose native launcher + // (apphost .exe) and dependency manifest live beside the module. .NET Framework + // assemblies have none, so the loop below simply finds nothing to copy. + string runtimeConfig = baseName + ".runtimeconfig.json"; + if (!File.Exists(Path.Combine(sourceDir, runtimeConfig))) + continue; + + string outputDir = Path.GetDirectoryName( + Path.GetFullPath(Path.Combine(context.OutputDirectory, context.OutputPaths[i]))); + if (outputDir is null) + continue; + + foreach (string supportFile in new[] { runtimeConfig, baseName + ".deps.json", baseName + ".exe" }) { + string from = Path.Combine(sourceDir, supportFile); + if (!File.Exists(from)) + continue; + string to = Path.Combine(outputDir, supportFile); + if (string.Equals(Path.GetFullPath(from), Path.GetFullPath(to), StringComparison.OrdinalIgnoreCase)) + continue; + if (!Directory.Exists(outputDir)) + Directory.CreateDirectory(outputDir); + context.Logger.LogDebug("Copying runtime support file '{0}'...", supportFile); + File.Copy(from, to, true); + } + } } /// diff --git a/Confuser.Core/Project/ConfuserPrj.xsd b/Confuser.Core/Project/ConfuserPrj.xsd index e99703b12..b85810939 100644 --- a/Confuser.Core/Project/ConfuserPrj.xsd +++ b/Confuser.Core/Project/ConfuserPrj.xsd @@ -75,6 +75,7 @@ + diff --git a/Confuser.Core/Project/ConfuserProject.cs b/Confuser.Core/Project/ConfuserProject.cs index 75d2f5361..43a362ec7 100644 --- a/Confuser.Core/Project/ConfuserProject.cs +++ b/Confuser.Core/Project/ConfuserProject.cs @@ -528,6 +528,15 @@ public ConfuserProject() { /// true if debug symbols are generated; otherwise, false. public bool Debug { get; set; } + /// + /// Gets or sets a value indicating whether the runtime support files of a .NET (Core) + /// application (its .runtimeconfig.json, .deps.json and native apphost + /// executable) are copied next to the obfuscated modules, so the output directory is + /// directly runnable. Has no effect on .NET Framework assemblies, which have none. + /// + /// true to copy the runtime support files; otherwise, false. Defaults to true. + public bool CopyRuntimeFiles { get; set; } = true; + /// /// Gets or sets the path to a symbol map from a previous obfuscation run. When set, the /// renamer reuses the obfuscated names recorded in that map, so re-obfuscation produces @@ -602,6 +611,12 @@ public XmlDocument Save() { elem.Attributes.Append(debugAttr); } + if (!CopyRuntimeFiles) { + XmlAttribute copyRuntimeAttr = xmlDoc.CreateAttribute("copyRuntimeFiles"); + copyRuntimeAttr.Value = CopyRuntimeFiles.ToString().ToLower(); + elem.Attributes.Append(copyRuntimeAttr); + } + if (InputSymbolMap != null) { XmlAttribute mapAttr = xmlDoc.CreateAttribute("inputSymbolMap"); mapAttr.Value = InputSymbolMap; @@ -669,6 +684,11 @@ public void Load(XmlDocument doc, string baseDirRoot = null) { else Debug = false; + if (docElem.Attributes["copyRuntimeFiles"] != null) + CopyRuntimeFiles = bool.Parse(docElem.Attributes["copyRuntimeFiles"].Value); + else + CopyRuntimeFiles = true; + if (docElem.Attributes["inputSymbolMap"] != null) InputSymbolMap = docElem.Attributes["inputSymbolMap"].Value.NullIfEmpty(); else @@ -743,6 +763,7 @@ public ConfuserProject Clone() { var ret = new ConfuserProject(); ret.Seed = Seed; ret.Debug = Debug; + ret.CopyRuntimeFiles = CopyRuntimeFiles; ret.InputSymbolMap = InputSymbolMap; ret.OutputDirectory = OutputDirectory; ret.BaseDirectory = BaseDirectory; diff --git a/Confuser2.sln b/Confuser2.sln index 9dc5bf919..88757d2fe 100644 --- a/Confuser2.sln +++ b/Confuser2.sln @@ -155,55 +155,55 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Confuser.CLI.Test", "Tests\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Confuser.GUI.Test", "Tests\Confuser.GUI.Test\Confuser.GUI.Test.csproj", "{C3EA99B7-7B9F-4589-BCCC-5D3D61C082B5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net10", "Tests\CrossFramework.Console.Net10\CrossFramework.Console.Net10.csproj", "{DA4385C6-BEEA-475F-947D-D06CC15AE36F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net10", "Tests\CrossFramework\Console\Net10\CrossFramework.Console.Net10.csproj", "{DA4385C6-BEEA-475F-947D-D06CC15AE36F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net20", "Tests\CrossFramework.Console.Net20\CrossFramework.Console.Net20.csproj", "{6D4C6502-8155-49DD-BBB1-7E99F5782738}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net20", "Tests\CrossFramework\Console\Net20\CrossFramework.Console.Net20.csproj", "{6D4C6502-8155-49DD-BBB1-7E99F5782738}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net35", "Tests\CrossFramework.Console.Net35\CrossFramework.Console.Net35.csproj", "{56A9138E-23D0-45AE-AA0C-EB27AC14064C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net35", "Tests\CrossFramework\Console\Net35\CrossFramework.Console.Net35.csproj", "{56A9138E-23D0-45AE-AA0C-EB27AC14064C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net40", "Tests\CrossFramework.Console.Net40\CrossFramework.Console.Net40.csproj", "{2B085FD5-3AC9-4E5E-A9B1-E8A7B0628FD7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net40", "Tests\CrossFramework\Console\Net40\CrossFramework.Console.Net40.csproj", "{2B085FD5-3AC9-4E5E-A9B1-E8A7B0628FD7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net48", "Tests\CrossFramework.Console.Net48\CrossFramework.Console.Net48.csproj", "{7DDCA8D5-BE85-4BD4-9B47-85B29D2FA3AF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net48", "Tests\CrossFramework\Console\Net48\CrossFramework.Console.Net48.csproj", "{7DDCA8D5-BE85-4BD4-9B47-85B29D2FA3AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net6", "Tests\CrossFramework.Console.Net6\CrossFramework.Console.Net6.csproj", "{FA75BDE0-9364-4A18-BF26-2110DA417797}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net6", "Tests\CrossFramework\Console\Net6\CrossFramework.Console.Net6.csproj", "{FA75BDE0-9364-4A18-BF26-2110DA417797}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net8", "Tests\CrossFramework.Console.Net8\CrossFramework.Console.Net8.csproj", "{9273835F-C47F-4705-87AE-0EF7BDBFC85A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Console.Net8", "Tests\CrossFramework\Console\Net8\CrossFramework.Console.Net8.csproj", "{9273835F-C47F-4705-87AE-0EF7BDBFC85A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net8", "Tests\CrossFramework.Library.Net8\CrossFramework.Library.Net8.csproj", "{3C326F28-33CE-44FC-89BE-2DAA0D087644}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net8", "Tests\CrossFramework\Library\Net8\CrossFramework.Library.Net8.csproj", "{3C326F28-33CE-44FC-89BE-2DAA0D087644}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.NetStd20", "Tests\CrossFramework.Library.NetStd20\CrossFramework.Library.NetStd20.csproj", "{61AC60A9-C462-44BB-B792-3D6EA1C1FFAA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.NetStd20", "Tests\CrossFramework\Library\NetStd20\CrossFramework.Library.NetStd20.csproj", "{61AC60A9-C462-44BB-B792-3D6EA1C1FFAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Test", "Tests\CrossFramework.Test\CrossFramework.Test.csproj", "{88CA9001-6138-4D0F-98D9-EC59A75FE1E8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Test", "Tests\CrossFramework\Test\CrossFramework.Test.csproj", "{88CA9001-6138-4D0F-98D9-EC59A75FE1E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net35", "Tests\CrossFramework.WinForms.Net35\CrossFramework.WinForms.Net35.csproj", "{EA611DF7-7FA6-4FA4-BE18-E6C569618FB0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net35", "Tests\CrossFramework\WinForms\Net35\CrossFramework.WinForms.Net35.csproj", "{EA611DF7-7FA6-4FA4-BE18-E6C569618FB0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net40", "Tests\CrossFramework.WinForms.Net40\CrossFramework.WinForms.Net40.csproj", "{1227C9C3-068C-4643-8E7C-8D90552243EE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net40", "Tests\CrossFramework\WinForms\Net40\CrossFramework.WinForms.Net40.csproj", "{1227C9C3-068C-4643-8E7C-8D90552243EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net48", "Tests\CrossFramework.WinForms.Net48\CrossFramework.WinForms.Net48.csproj", "{8A95BAB0-ACFF-43B5-BA77-A78A3C24611B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net48", "Tests\CrossFramework\WinForms\Net48\CrossFramework.WinForms.Net48.csproj", "{8A95BAB0-ACFF-43B5-BA77-A78A3C24611B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net10", "Tests\CrossFramework.WinForms.Net10\CrossFramework.WinForms.Net10.csproj", "{0323A3B1-8607-4914-894F-08BBBD1D8618}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net10", "Tests\CrossFramework\WinForms\Net10\CrossFramework.WinForms.Net10.csproj", "{0323A3B1-8607-4914-894F-08BBBD1D8618}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net35", "Tests\CrossFramework.WPF.Net35\CrossFramework.WPF.Net35.csproj", "{CB4835D2-06BC-472F-9B5A-D3844C5D8D81}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net35", "Tests\CrossFramework\WPF\Net35\CrossFramework.WPF.Net35.csproj", "{CB4835D2-06BC-472F-9B5A-D3844C5D8D81}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net40", "Tests\CrossFramework.WPF.Net40\CrossFramework.WPF.Net40.csproj", "{79A8C4D6-2199-4B0D-BCBE-E0FCA93A8BDC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net40", "Tests\CrossFramework\WPF\Net40\CrossFramework.WPF.Net40.csproj", "{79A8C4D6-2199-4B0D-BCBE-E0FCA93A8BDC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net48", "Tests\CrossFramework.WPF.Net48\CrossFramework.WPF.Net48.csproj", "{EB8D610B-E413-4C6E-97A2-AA92EA2C1DA0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net48", "Tests\CrossFramework\WPF\Net48\CrossFramework.WPF.Net48.csproj", "{EB8D610B-E413-4C6E-97A2-AA92EA2C1DA0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net10", "Tests\CrossFramework.WPF.Net10\CrossFramework.WPF.Net10.csproj", "{DE876679-5C28-45B3-BDFB-952C9C586CA1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net10", "Tests\CrossFramework\WPF\Net10\CrossFramework.WPF.Net10.csproj", "{DE876679-5C28-45B3-BDFB-952C9C586CA1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net6", "Tests\CrossFramework.WinForms.Net6\CrossFramework.WinForms.Net6.csproj", "{3FB8F727-BD28-455C-A14A-09DCE7C42E90}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net6", "Tests\CrossFramework\WinForms\Net6\CrossFramework.WinForms.Net6.csproj", "{3FB8F727-BD28-455C-A14A-09DCE7C42E90}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net8", "Tests\CrossFramework.WinForms.Net8\CrossFramework.WinForms.Net8.csproj", "{424E07DB-C32E-4AAE-BE50-D839AF541041}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WinForms.Net8", "Tests\CrossFramework\WinForms\Net8\CrossFramework.WinForms.Net8.csproj", "{424E07DB-C32E-4AAE-BE50-D839AF541041}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net48", "Tests\CrossFramework.Library.Net48\CrossFramework.Library.Net48.csproj", "{EF581C5C-5AAB-4A35-A2E4-1583CB4FDB9F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net48", "Tests\CrossFramework\Library\Net48\CrossFramework.Library.Net48.csproj", "{EF581C5C-5AAB-4A35-A2E4-1583CB4FDB9F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net6", "Tests\CrossFramework.Library.Net6\CrossFramework.Library.Net6.csproj", "{0E65E38D-3A0D-42DB-ACD1-38096CE56092}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net6", "Tests\CrossFramework\Library\Net6\CrossFramework.Library.Net6.csproj", "{0E65E38D-3A0D-42DB-ACD1-38096CE56092}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net6", "Tests\CrossFramework.WPF.Net6\CrossFramework.WPF.Net6.csproj", "{3942E3FD-06BC-470C-A1ED-BB18F2332B94}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net6", "Tests\CrossFramework\WPF\Net6\CrossFramework.WPF.Net6.csproj", "{3942E3FD-06BC-470C-A1ED-BB18F2332B94}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net8", "Tests\CrossFramework.WPF.Net8\CrossFramework.WPF.Net8.csproj", "{30DC9F52-E08A-4EF0-B041-04AED6135C3D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net8", "Tests\CrossFramework\WPF\Net8\CrossFramework.WPF.Net8.csproj", "{30DC9F52-E08A-4EF0-B041-04AED6135C3D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net10", "Tests\CrossFramework.Library.Net10\CrossFramework.Library.Net10.csproj", "{4458415A-0F5E-4136-B723-7A67955D6047}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net10", "Tests\CrossFramework\Library\Net10\CrossFramework.Library.Net10.csproj", "{4458415A-0F5E-4136-B723-7A67955D6047}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Confuser.Analyzers", "Confuser.Analyzers\Confuser.Analyzers.csproj", "{886E5FF3-DF94-4C6B-A5C3-E97038B5C275}" EndProject diff --git a/Tests/Confuser.UnitTest/TestBase.cs b/Tests/Confuser.UnitTest/TestBase.cs index c88b0403d..792471e0d 100644 --- a/Tests/Confuser.UnitTest/TestBase.cs +++ b/Tests/Confuser.UnitTest/TestBase.cs @@ -24,10 +24,10 @@ protected TestBase(ITestOutputHelper outputHelper) => protected Task Run(string inputFileName, string[] expectedOutput, SettingItem protection, string outputDirSuffix = "", Action outputAction = null, SettingItem packer = null, Action projectModuleAction = null, Func postProcessAction = null, - string seed = null, bool checkOutput = true, string processArguments = null) => + string seed = null, bool checkOutput = true, string processArguments = null, bool runEntryProcess = true) => Run(new[] { inputFileName }, expectedOutput, protection, outputDirSuffix, outputAction, packer, - projectModuleAction, postProcessAction, seed, checkOutput, processArguments); + projectModuleAction, postProcessAction, seed, checkOutput, processArguments, runEntryProcess); protected Task Run(string inputFileName, string[] expectedOutput, IEnumerable> protections, string outputDirSuffix = "", Action outputAction = null, SettingItem packer = null, @@ -40,15 +40,15 @@ protected Task Run(string inputFileName, string[] expectedOutput, IEnumerable protection, string outputDirSuffix = "", Action outputAction = null, SettingItem packer = null, Action projectModuleAction = null, Func postProcessAction = null, - string seed = null, bool checkOutput = true, string processArguments = null) { + string seed = null, bool checkOutput = true, string processArguments = null, bool runEntryProcess = true) { var protections = (protection is null) ? Enumerable.Empty>() : new[] { protection }; - return Run(inputFileNames, expectedOutput, protections, outputDirSuffix, outputAction, packer, projectModuleAction, postProcessAction, seed, checkOutput, processArguments); + return Run(inputFileNames, expectedOutput, protections, outputDirSuffix, outputAction, packer, projectModuleAction, postProcessAction, seed, checkOutput, processArguments, runEntryProcess); } protected async Task Run(string[] inputFileNames, string[] expectedOutput, IEnumerable> protections, string outputDirSuffix = "", Action outputAction = null, SettingItem packer = null, Action projectModuleAction = null, Func postProcessAction = null, - string seed = null, bool checkOutput = true, string processArguments = null) { + string seed = null, bool checkOutput = true, string processArguments = null, bool runEntryProcess = true) { var baseDir = Environment.CurrentDirectory; var outputDir = Path.Combine(baseDir, "obfuscated" + outputDirSuffix); @@ -104,19 +104,20 @@ protected async Task Run(string[] inputFileNames, string[] expectedOutput, IEnum exists = File.Exists(outputName); } - if (exists) { - // Check if output assemblies is obfuscated + if (exists && IsAssembly(inputFileNames[index])) { + // Check if output assemblies is obfuscated. Non-assembly support files + // (e.g. runtimeconfig.json copied next to the module) are verbatim copies. Assert.NotEqual(FileUtilities.ComputeFileChecksum(Path.Combine(baseDir, name)), FileUtilities.ComputeFileChecksum(outputName)); } - else if (IsExternal(inputFileNames[index])) { + else if (!exists && IsExternal(inputFileNames[index])) { File.Copy( Path.Combine(baseDir, GetFileName(inputFileNames[index])), Path.Combine(outputDir, GetFileName(inputFileNames[index]))); } } - if (Path.GetExtension(entryInputFileName) == ".exe") { + if (runEntryProcess && Path.GetExtension(entryInputFileName) == ".exe") { var info = new ProcessStartInfo(entryOutputFileName) { RedirectStandardOutput = true, RedirectStandardError = true, @@ -185,5 +186,84 @@ private static bool IsAssembly(string name) { return extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) || extension.Equals(".exe", StringComparison.OrdinalIgnoreCase); } + + /// + /// Runs an obfuscated application from its output directory and returns the exit code + /// together with the captured output streams. A .exe (.NET Framework) is launched + /// directly; a .dll (.NET Core) is launched via dotnet <dll>. + /// + protected static (int ExitCode, string StdOut, string StdErr) RunApp( + string workingDirectory, string appFileName, string arguments = null) { + ProcessStartInfo startInfo; + if (appFileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { + startInfo = new ProcessStartInfo(Path.Combine(workingDirectory, appFileName)) { + Arguments = arguments ?? "", + WorkingDirectory = workingDirectory + }; + } + else { + startInfo = new ProcessStartInfo("dotnet", + string.IsNullOrEmpty(arguments) ? appFileName : appFileName + " " + arguments) { + WorkingDirectory = workingDirectory + }; + } + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + + using var process = Process.Start(startInfo); + string stdout = process.StandardOutput.ReadToEnd(); + string stderr = process.StandardError.ReadToEnd(); + process.WaitForExit(); + return (process.ExitCode, stdout, stderr); + } + + /// + /// Obfuscates the given UI application, then launches the obfuscated build with + /// --selftest: it shows its real window off-screen, pumps its message loop and + /// self-closes. Asserts the app actually started and exited cleanly (42) without crashing. + /// + protected Task SelfTestRunsWithoutCrashing(string appFile, string outputDirSuffix) => + Run(appFile, null, new SettingItem("rename"), + outputDirSuffix: outputDirSuffix, checkOutput: false, runEntryProcess: false, + postProcessAction: outputPath => { + var (exit, stdout, stderr) = RunApp(outputPath, appFile, "--selftest"); + Assert.Equal(42, exit); + Assert.Contains("SHOWN:", stdout); + Assert.DoesNotContain("CRASH", stdout); + Assert.Empty(stderr); + return Task.CompletedTask; + }); + + /// + /// Obfuscates the given UI application, then launches it with --selftest-crash, + /// which throws once the window is shown. Asserts the app's unhandled-exception handler + /// caught the crash and reported failure (non-42 exit, CRASH on stdout). + /// + /// + /// Obfuscates the given console application, then runs the obfuscated build with + /// --selftest and asserts it executed to completion and exited cleanly (42). + /// + protected Task SelfTestConsoleRuns(string appFile, string outputDirSuffix) => + Run(appFile, null, new SettingItem("rename"), + outputDirSuffix: outputDirSuffix, checkOutput: false, runEntryProcess: false, + postProcessAction: outputPath => { + var (exit, stdout, stderr) = RunApp(outputPath, appFile, "--selftest"); + Assert.Equal(42, exit); + Assert.Contains("RUN", stdout); + Assert.DoesNotContain("CRASH", stdout); + Assert.Empty(stderr); + return Task.CompletedTask; + }); + + protected Task SelfTestReportsCrash(string appFile, string outputDirSuffix) => + Run(appFile, null, new SettingItem("rename"), + outputDirSuffix: outputDirSuffix, checkOutput: false, runEntryProcess: false, + postProcessAction: outputPath => { + var (exit, stdout, _) = RunApp(outputPath, appFile, "--selftest-crash"); + Assert.NotEqual(42, exit); + Assert.Contains("CRASH", stdout); + return Task.CompletedTask; + }); } } diff --git a/Tests/CrossFramework.Test/CrossFramework.Test.csproj b/Tests/CrossFramework.Test/CrossFramework.Test.csproj deleted file mode 100644 index 8ce559c21..000000000 --- a/Tests/CrossFramework.Test/CrossFramework.Test.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net10.0-windows - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/CrossFramework.Test/WinFormsFrameworkTest.cs b/Tests/CrossFramework.Test/WinFormsFrameworkTest.cs deleted file mode 100644 index b6fecf658..000000000 --- a/Tests/CrossFramework.Test/WinFormsFrameworkTest.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Threading.Tasks; -using Confuser.Core; -using Confuser.Core.Project; -using Confuser.UnitTest; -using Xunit; -using Xunit.Abstractions; - -namespace CrossFramework.Test { - public class WinFormsFrameworkTest : TestBase { - public WinFormsFrameworkTest(ITestOutputHelper outputHelper) : base(outputHelper) { } - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net35")] - public Task WinForms_Net35_RenameProtection() => - Run("CrossFramework.WinForms.Net35.exe", - new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net35)" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-winforms-net35"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net40")] - public Task WinForms_Net40_RenameProtection() => - Run("CrossFramework.WinForms.Net40.exe", - new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net40)" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-winforms-net40"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net48")] - public Task WinForms_Net48_RenameProtection() => - Run("CrossFramework.WinForms.Net48.exe", - new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net48)" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-winforms-net48"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net6.0-windows")] - public Task WinForms_Net6_RenameProtection() => - Run("CrossFramework.WinForms.Net6.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-winforms-net6", - checkOutput: false); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net8.0-windows")] - public Task WinForms_Net8_RenameProtection() => - Run("CrossFramework.WinForms.Net8.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-winforms-net8", - checkOutput: false); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WinForms")] - [Trait("TFM", "net10.0-windows")] - public Task WinForms_Net10_RenameProtection() => - Run("CrossFramework.WinForms.Net10.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-winforms-net10", - checkOutput: false); - } -} diff --git a/Tests/CrossFramework.Test/WpfFrameworkTest.cs b/Tests/CrossFramework.Test/WpfFrameworkTest.cs deleted file mode 100644 index b60030a41..000000000 --- a/Tests/CrossFramework.Test/WpfFrameworkTest.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Threading.Tasks; -using Confuser.Core; -using Confuser.Core.Project; -using Confuser.UnitTest; -using Xunit; -using Xunit.Abstractions; - -namespace CrossFramework.Test { - public class WpfFrameworkTest : TestBase { - public WpfFrameworkTest(ITestOutputHelper outputHelper) : base(outputHelper) { } - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net35")] - public Task WPF_Net35_RenameProtection() => - Run("CrossFramework.WPF.Net35.exe", - new[] { "Title: ConfuserEx WPF Test (net35)", "Content: WPF is working" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-wpf-net35"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net40")] - public Task WPF_Net40_RenameProtection() => - Run("CrossFramework.WPF.Net40.exe", - new[] { "Title: ConfuserEx WPF Test (net40)", "Content: WPF is working" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-wpf-net40"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net48")] - public Task WPF_Net48_RenameProtection() => - Run("CrossFramework.WPF.Net48.exe", - new[] { "Title: ConfuserEx WPF Test (Net48)", "Content: WPF is working" }, - new SettingItem("rename"), - processArguments: "--verify", - outputDirSuffix: "-wpf-net48"); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net6.0-windows")] - public Task WPF_Net6_RenameProtection() => - Run("CrossFramework.WPF.Net6.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-wpf-net6", - checkOutput: false); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net8.0-windows")] - public Task WPF_Net8_RenameProtection() => - Run("CrossFramework.WPF.Net8.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-wpf-net8", - checkOutput: false); - - [Fact] - [Trait("Category", "CrossFramework")] - [Trait("AppType", "WPF")] - [Trait("TFM", "net10.0-windows")] - public Task WPF_Net10_RenameProtection() => - Run("CrossFramework.WPF.Net10.dll", - null, - new SettingItem("rename"), - outputDirSuffix: "-wpf-net10", - checkOutput: false); - } -} diff --git a/Tests/CrossFramework.Console.Net10/CrossFramework.Console.Net10.csproj b/Tests/CrossFramework/Console/Net10/CrossFramework.Console.Net10.csproj similarity index 100% rename from Tests/CrossFramework.Console.Net10/CrossFramework.Console.Net10.csproj rename to Tests/CrossFramework/Console/Net10/CrossFramework.Console.Net10.csproj diff --git a/Tests/CrossFramework.Console.Net10/Program.cs b/Tests/CrossFramework/Console/Net10/Program.cs similarity index 100% rename from Tests/CrossFramework.Console.Net10/Program.cs rename to Tests/CrossFramework/Console/Net10/Program.cs diff --git a/Tests/CrossFramework.Console.Net10/Resource.txt b/Tests/CrossFramework/Console/Net10/Resource.txt similarity index 100% rename from Tests/CrossFramework.Console.Net10/Resource.txt rename to Tests/CrossFramework/Console/Net10/Resource.txt diff --git a/Tests/CrossFramework.Console.Net20/CrossFramework.Console.Net20.csproj b/Tests/CrossFramework/Console/Net20/CrossFramework.Console.Net20.csproj similarity index 100% rename from Tests/CrossFramework.Console.Net20/CrossFramework.Console.Net20.csproj rename to Tests/CrossFramework/Console/Net20/CrossFramework.Console.Net20.csproj diff --git a/Tests/CrossFramework.Console.Net20/Program.cs b/Tests/CrossFramework/Console/Net20/Program.cs similarity index 100% rename from Tests/CrossFramework.Console.Net20/Program.cs rename to Tests/CrossFramework/Console/Net20/Program.cs diff --git a/Tests/CrossFramework.Console.Net35/CrossFramework.Console.Net35.csproj b/Tests/CrossFramework/Console/Net35/CrossFramework.Console.Net35.csproj similarity index 100% rename from Tests/CrossFramework.Console.Net35/CrossFramework.Console.Net35.csproj rename to Tests/CrossFramework/Console/Net35/CrossFramework.Console.Net35.csproj diff --git a/Tests/CrossFramework.Console.Net35/Program.cs b/Tests/CrossFramework/Console/Net35/Program.cs similarity index 100% rename from Tests/CrossFramework.Console.Net35/Program.cs rename to Tests/CrossFramework/Console/Net35/Program.cs diff --git a/Tests/CrossFramework.Console.Net40/CrossFramework.Console.Net40.csproj b/Tests/CrossFramework/Console/Net40/CrossFramework.Console.Net40.csproj similarity index 100% rename from Tests/CrossFramework.Console.Net40/CrossFramework.Console.Net40.csproj rename to Tests/CrossFramework/Console/Net40/CrossFramework.Console.Net40.csproj diff --git a/Tests/CrossFramework.Console.Net40/Program.cs b/Tests/CrossFramework/Console/Net40/Program.cs similarity index 100% rename from Tests/CrossFramework.Console.Net40/Program.cs rename to Tests/CrossFramework/Console/Net40/Program.cs diff --git a/Tests/CrossFramework.Console.Net48/CrossFramework.Console.Net48.csproj b/Tests/CrossFramework/Console/Net48/CrossFramework.Console.Net48.csproj similarity index 100% rename from Tests/CrossFramework.Console.Net48/CrossFramework.Console.Net48.csproj rename to Tests/CrossFramework/Console/Net48/CrossFramework.Console.Net48.csproj diff --git a/Tests/CrossFramework.Console.Net48/Program.cs b/Tests/CrossFramework/Console/Net48/Program.cs similarity index 100% rename from Tests/CrossFramework.Console.Net48/Program.cs rename to Tests/CrossFramework/Console/Net48/Program.cs diff --git a/Tests/CrossFramework.Console.Net6/CrossFramework.Console.Net6.csproj b/Tests/CrossFramework/Console/Net6/CrossFramework.Console.Net6.csproj similarity index 62% rename from Tests/CrossFramework.Console.Net6/CrossFramework.Console.Net6.csproj rename to Tests/CrossFramework/Console/Net6/CrossFramework.Console.Net6.csproj index b87b9174f..01d184acc 100644 --- a/Tests/CrossFramework.Console.Net6/CrossFramework.Console.Net6.csproj +++ b/Tests/CrossFramework/Console/Net6/CrossFramework.Console.Net6.csproj @@ -4,4 +4,7 @@ net6.0 disable + + + diff --git a/Tests/CrossFramework.Console.Net6/Program.cs b/Tests/CrossFramework/Console/Net6/Program.cs similarity index 50% rename from Tests/CrossFramework.Console.Net6/Program.cs rename to Tests/CrossFramework/Console/Net6/Program.cs index adf77bd1d..df86447f4 100644 --- a/Tests/CrossFramework.Console.Net6/Program.cs +++ b/Tests/CrossFramework/Console/Net6/Program.cs @@ -2,7 +2,10 @@ namespace CrossFramework.Console { class Program { - static int Main() { + static int Main(string[] args) { + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.ConsoleSelfTestHost.Run(args[0] == "--selftest-crash"); + System.Console.WriteLine("START"); System.Console.WriteLine("Hello from net6.0"); System.Console.WriteLine("END"); diff --git a/Tests/CrossFramework.Console.Net8/CrossFramework.Console.Net8.csproj b/Tests/CrossFramework/Console/Net8/CrossFramework.Console.Net8.csproj similarity index 62% rename from Tests/CrossFramework.Console.Net8/CrossFramework.Console.Net8.csproj rename to Tests/CrossFramework/Console/Net8/CrossFramework.Console.Net8.csproj index d0ab6f9a4..4a7a33a58 100644 --- a/Tests/CrossFramework.Console.Net8/CrossFramework.Console.Net8.csproj +++ b/Tests/CrossFramework/Console/Net8/CrossFramework.Console.Net8.csproj @@ -4,4 +4,7 @@ net8.0 disable + + + diff --git a/Tests/CrossFramework.Console.Net8/Program.cs b/Tests/CrossFramework/Console/Net8/Program.cs similarity index 50% rename from Tests/CrossFramework.Console.Net8/Program.cs rename to Tests/CrossFramework/Console/Net8/Program.cs index 779a97411..c10659acb 100644 --- a/Tests/CrossFramework.Console.Net8/Program.cs +++ b/Tests/CrossFramework/Console/Net8/Program.cs @@ -2,7 +2,10 @@ namespace CrossFramework.Console { class Program { - static int Main() { + static int Main(string[] args) { + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.ConsoleSelfTestHost.Run(args[0] == "--selftest-crash"); + System.Console.WriteLine("START"); System.Console.WriteLine("Hello from net8.0"); System.Console.WriteLine("END"); diff --git a/Tests/CrossFramework.Library.Net10/CrossFramework.Library.Net10.csproj b/Tests/CrossFramework/Library/Net10/CrossFramework.Library.Net10.csproj similarity index 100% rename from Tests/CrossFramework.Library.Net10/CrossFramework.Library.Net10.csproj rename to Tests/CrossFramework/Library/Net10/CrossFramework.Library.Net10.csproj diff --git a/Tests/CrossFramework.Library.Net10/SampleService.cs b/Tests/CrossFramework/Library/Net10/SampleService.cs similarity index 100% rename from Tests/CrossFramework.Library.Net10/SampleService.cs rename to Tests/CrossFramework/Library/Net10/SampleService.cs diff --git a/Tests/CrossFramework.Library.Net48/CrossFramework.Library.Net48.csproj b/Tests/CrossFramework/Library/Net48/CrossFramework.Library.Net48.csproj similarity index 100% rename from Tests/CrossFramework.Library.Net48/CrossFramework.Library.Net48.csproj rename to Tests/CrossFramework/Library/Net48/CrossFramework.Library.Net48.csproj diff --git a/Tests/CrossFramework.Library.Net48/SampleService.cs b/Tests/CrossFramework/Library/Net48/SampleService.cs similarity index 100% rename from Tests/CrossFramework.Library.Net48/SampleService.cs rename to Tests/CrossFramework/Library/Net48/SampleService.cs diff --git a/Tests/CrossFramework.Library.Net6/CrossFramework.Library.Net6.csproj b/Tests/CrossFramework/Library/Net6/CrossFramework.Library.Net6.csproj similarity index 100% rename from Tests/CrossFramework.Library.Net6/CrossFramework.Library.Net6.csproj rename to Tests/CrossFramework/Library/Net6/CrossFramework.Library.Net6.csproj diff --git a/Tests/CrossFramework.Library.Net6/SampleService.cs b/Tests/CrossFramework/Library/Net6/SampleService.cs similarity index 100% rename from Tests/CrossFramework.Library.Net6/SampleService.cs rename to Tests/CrossFramework/Library/Net6/SampleService.cs diff --git a/Tests/CrossFramework.Library.Net8/CrossFramework.Library.Net8.csproj b/Tests/CrossFramework/Library/Net8/CrossFramework.Library.Net8.csproj similarity index 100% rename from Tests/CrossFramework.Library.Net8/CrossFramework.Library.Net8.csproj rename to Tests/CrossFramework/Library/Net8/CrossFramework.Library.Net8.csproj diff --git a/Tests/CrossFramework.Library.Net8/SampleService.cs b/Tests/CrossFramework/Library/Net8/SampleService.cs similarity index 100% rename from Tests/CrossFramework.Library.Net8/SampleService.cs rename to Tests/CrossFramework/Library/Net8/SampleService.cs diff --git a/Tests/CrossFramework.Library.NetStd20/CrossFramework.Library.NetStd20.csproj b/Tests/CrossFramework/Library/NetStd20/CrossFramework.Library.NetStd20.csproj similarity index 100% rename from Tests/CrossFramework.Library.NetStd20/CrossFramework.Library.NetStd20.csproj rename to Tests/CrossFramework/Library/NetStd20/CrossFramework.Library.NetStd20.csproj diff --git a/Tests/CrossFramework.Library.NetStd20/SampleService.cs b/Tests/CrossFramework/Library/NetStd20/SampleService.cs similarity index 100% rename from Tests/CrossFramework.Library.NetStd20/SampleService.cs rename to Tests/CrossFramework/Library/NetStd20/SampleService.cs diff --git a/Tests/CrossFramework.Test/ConsoleFrameworkTest.cs b/Tests/CrossFramework/Test/ConsoleFrameworkTest.cs similarity index 58% rename from Tests/CrossFramework.Test/ConsoleFrameworkTest.cs rename to Tests/CrossFramework/Test/ConsoleFrameworkTest.cs index bea1b7119..9fd6146ed 100644 --- a/Tests/CrossFramework.Test/ConsoleFrameworkTest.cs +++ b/Tests/CrossFramework/Test/ConsoleFrameworkTest.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; using Confuser.Core; using Confuser.Core.Project; @@ -117,5 +118,55 @@ public Task Console_Net10_ResourceProtection() => output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)); return Task.CompletedTask; }); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "Console")] + [Trait("TFM", "net10.0")] + [Trait("Issue", "https://github.com/mcpolo99/ConfuserExx/issues/103")] + public Task Console_Net10_EmitsRunnableOutput() => + Run("CrossFramework.Console.Net10.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-console-net10-runnable", + checkOutput: false, + postProcessAction: outputPath => { + // The obfuscated output must be directly runnable: the copyRuntimeFiles feature + // copies the .NET runtime support files next to the obfuscated module. + Assert.True(File.Exists(Path.Combine(outputPath, "CrossFramework.Console.Net10.runtimeconfig.json")), + "runtimeconfig.json was not copied into the obfuscated output"); + Assert.True(File.Exists(Path.Combine(outputPath, "CrossFramework.Console.Net10.deps.json")), + "deps.json was not copied into the obfuscated output"); + Assert.True(File.Exists(Path.Combine(outputPath, "CrossFramework.Console.Net10.exe")), + "apphost .exe was not copied into the obfuscated output"); + + var startInfo = new ProcessStartInfo("dotnet", "CrossFramework.Console.Net10.dll") { + WorkingDirectory = outputPath, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + using var process = Process.Start(startInfo); + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + Assert.Equal(42, process.ExitCode); + Assert.Empty(error); + Assert.Contains("Hello from net10.0", output); + return Task.CompletedTask; + }); + + // --- Run the obfuscated console apps that were previously obfuscated-but-never-executed, + // with crash reporting via the unhandled-exception handler. --- + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "Console"), Trait("TFM", "net6.0"), Trait("Issue", "103")] + public Task Console_Net6_SelfTest_Runs() => SelfTestConsoleRuns("CrossFramework.Console.Net6.dll", "-console-net6-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "Console"), Trait("TFM", "net6.0"), Trait("Issue", "103")] + public Task Console_Net6_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.Console.Net6.dll", "-console-net6-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "Console"), Trait("TFM", "net8.0"), Trait("Issue", "103")] + public Task Console_Net8_SelfTest_Runs() => SelfTestConsoleRuns("CrossFramework.Console.Net8.dll", "-console-net8-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "Console"), Trait("TFM", "net8.0"), Trait("Issue", "103")] + public Task Console_Net8_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.Console.Net8.dll", "-console-net8-crash"); } } diff --git a/Tests/CrossFramework/Test/CrossFramework.Test.csproj b/Tests/CrossFramework/Test/CrossFramework.Test.csproj new file mode 100644 index 000000000..c95e7526b --- /dev/null +++ b/Tests/CrossFramework/Test/CrossFramework.Test.csproj @@ -0,0 +1,40 @@ + + + + net10.0-windows + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/CrossFramework.Test/LibraryFrameworkTest.cs b/Tests/CrossFramework/Test/LibraryFrameworkTest.cs similarity index 100% rename from Tests/CrossFramework.Test/LibraryFrameworkTest.cs rename to Tests/CrossFramework/Test/LibraryFrameworkTest.cs diff --git a/Tests/CrossFramework/Test/WinFormsFrameworkTest.cs b/Tests/CrossFramework/Test/WinFormsFrameworkTest.cs new file mode 100644 index 000000000..37ecacb07 --- /dev/null +++ b/Tests/CrossFramework/Test/WinFormsFrameworkTest.cs @@ -0,0 +1,112 @@ +using System.Threading.Tasks; +using Confuser.Core; +using Confuser.Core.Project; +using Confuser.UnitTest; +using Xunit; +using Xunit.Abstractions; + +namespace CrossFramework.Test { + public class WinFormsFrameworkTest : TestBase { + public WinFormsFrameworkTest(ITestOutputHelper outputHelper) : base(outputHelper) { } + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net35")] + public Task WinForms_Net35_RenameProtection() => + Run("CrossFramework.WinForms.Net35.exe", + new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net35)" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-winforms-net35"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net40")] + public Task WinForms_Net40_RenameProtection() => + Run("CrossFramework.WinForms.Net40.exe", + new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net40)" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-winforms-net40"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net48")] + public Task WinForms_Net48_RenameProtection() => + Run("CrossFramework.WinForms.Net48.exe", + new[] { "Label: Not clicked", "Title: ConfuserEx WinForms Test (net48)" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-winforms-net48"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net6.0-windows")] + public Task WinForms_Net6_RenameProtection() => + Run("CrossFramework.WinForms.Net6.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-winforms-net6", + checkOutput: false); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net8.0-windows")] + public Task WinForms_Net8_RenameProtection() => + Run("CrossFramework.WinForms.Net8.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-winforms-net8", + checkOutput: false); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WinForms")] + [Trait("TFM", "net10.0-windows")] + public Task WinForms_Net10_RenameProtection() => + Run("CrossFramework.WinForms.Net10.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-winforms-net10", + checkOutput: false); + + // --- Actually run the obfuscated UI app on every framework: it shows its real window + // off-screen, pumps the message loop, self-closes after ~2s (exit 42), and an induced + // unhandled exception is caught by the crash handlers and reported as failure. --- + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net35"), Trait("Issue", "103")] + public Task WinForms_Net35_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net35.exe", "-winforms-net35-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net35"), Trait("Issue", "103")] + public Task WinForms_Net35_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net35.exe", "-winforms-net35-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net40"), Trait("Issue", "103")] + public Task WinForms_Net40_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net40.exe", "-winforms-net40-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net40"), Trait("Issue", "103")] + public Task WinForms_Net40_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net40.exe", "-winforms-net40-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net48"), Trait("Issue", "103")] + public Task WinForms_Net48_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net48.exe", "-winforms-net48-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net48"), Trait("Issue", "103")] + public Task WinForms_Net48_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net48.exe", "-winforms-net48-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net6.0"), Trait("Issue", "103")] + public Task WinForms_Net6_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net6.dll", "-winforms-net6-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net6.0"), Trait("Issue", "103")] + public Task WinForms_Net6_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net6.dll", "-winforms-net6-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net8.0"), Trait("Issue", "103")] + public Task WinForms_Net8_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net8.dll", "-winforms-net8-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net8.0"), Trait("Issue", "103")] + public Task WinForms_Net8_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net8.dll", "-winforms-net8-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net10.0"), Trait("Issue", "103")] + public Task WinForms_Net10_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WinForms.Net10.dll", "-winforms-net10-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WinForms"), Trait("TFM", "net10.0"), Trait("Issue", "103")] + public Task WinForms_Net10_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WinForms.Net10.dll", "-winforms-net10-crash"); + } +} diff --git a/Tests/CrossFramework/Test/WpfFrameworkTest.cs b/Tests/CrossFramework/Test/WpfFrameworkTest.cs new file mode 100644 index 000000000..ec7bc7b0b --- /dev/null +++ b/Tests/CrossFramework/Test/WpfFrameworkTest.cs @@ -0,0 +1,112 @@ +using System.Threading.Tasks; +using Confuser.Core; +using Confuser.Core.Project; +using Confuser.UnitTest; +using Xunit; +using Xunit.Abstractions; + +namespace CrossFramework.Test { + public class WpfFrameworkTest : TestBase { + public WpfFrameworkTest(ITestOutputHelper outputHelper) : base(outputHelper) { } + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net35")] + public Task WPF_Net35_RenameProtection() => + Run("CrossFramework.WPF.Net35.exe", + new[] { "Title: ConfuserEx WPF Test (net35)", "Content: WPF is working" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-wpf-net35"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net40")] + public Task WPF_Net40_RenameProtection() => + Run("CrossFramework.WPF.Net40.exe", + new[] { "Title: ConfuserEx WPF Test (net40)", "Content: WPF is working" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-wpf-net40"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net48")] + public Task WPF_Net48_RenameProtection() => + Run("CrossFramework.WPF.Net48.exe", + new[] { "Title: ConfuserEx WPF Test (Net48)", "Content: WPF is working" }, + new SettingItem("rename"), + processArguments: "--verify", + outputDirSuffix: "-wpf-net48"); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net6.0-windows")] + public Task WPF_Net6_RenameProtection() => + Run("CrossFramework.WPF.Net6.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-wpf-net6", + checkOutput: false); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net8.0-windows")] + public Task WPF_Net8_RenameProtection() => + Run("CrossFramework.WPF.Net8.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-wpf-net8", + checkOutput: false); + + [Fact] + [Trait("Category", "CrossFramework")] + [Trait("AppType", "WPF")] + [Trait("TFM", "net10.0-windows")] + public Task WPF_Net10_RenameProtection() => + Run("CrossFramework.WPF.Net10.dll", + null, + new SettingItem("rename"), + outputDirSuffix: "-wpf-net10", + checkOutput: false); + + // --- Actually run the obfuscated WPF app on every framework: it shows its real window + // off-screen/invisible, pumps the dispatcher, self-terminates (exit 42), and an induced + // unhandled exception is caught by the crash handlers and reported as failure. --- + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net35"), Trait("Issue", "103")] + public Task WPF_Net35_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net35.exe", "-wpf-net35-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net35"), Trait("Issue", "103")] + public Task WPF_Net35_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net35.exe", "-wpf-net35-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net40"), Trait("Issue", "103")] + public Task WPF_Net40_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net40.exe", "-wpf-net40-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net40"), Trait("Issue", "103")] + public Task WPF_Net40_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net40.exe", "-wpf-net40-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net48"), Trait("Issue", "103")] + public Task WPF_Net48_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net48.exe", "-wpf-net48-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net48"), Trait("Issue", "103")] + public Task WPF_Net48_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net48.exe", "-wpf-net48-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net6.0-windows"), Trait("Issue", "103")] + public Task WPF_Net6_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net6.dll", "-wpf-net6-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net6.0-windows"), Trait("Issue", "103")] + public Task WPF_Net6_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net6.dll", "-wpf-net6-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net8.0-windows"), Trait("Issue", "103")] + public Task WPF_Net8_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net8.dll", "-wpf-net8-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net8.0-windows"), Trait("Issue", "103")] + public Task WPF_Net8_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net8.dll", "-wpf-net8-crash"); + + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net10.0-windows"), Trait("Issue", "103")] + public Task WPF_Net10_SelfTest_Runs() => SelfTestRunsWithoutCrashing("CrossFramework.WPF.Net10.dll", "-wpf-net10-selftest"); + [Fact, Trait("Category", "CrossFramework"), Trait("AppType", "WPF"), Trait("TFM", "net10.0-windows"), Trait("Issue", "103")] + public Task WPF_Net10_SelfTest_Crash() => SelfTestReportsCrash("CrossFramework.WPF.Net10.dll", "-wpf-net10-crash"); + } +} diff --git a/Tests/CrossFramework.WPF.Net10/App.xaml b/Tests/CrossFramework/WPF/Net10/App.xaml similarity index 81% rename from Tests/CrossFramework.WPF.Net10/App.xaml rename to Tests/CrossFramework/WPF/Net10/App.xaml index a13360fb3..26cf87f1d 100644 --- a/Tests/CrossFramework.WPF.Net10/App.xaml +++ b/Tests/CrossFramework/WPF/Net10/App.xaml @@ -1,5 +1,4 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/Tests/CrossFramework.WPF.Net48/App.xaml.cs b/Tests/CrossFramework/WPF/Net10/App.xaml.cs similarity index 63% rename from Tests/CrossFramework.WPF.Net48/App.xaml.cs rename to Tests/CrossFramework/WPF/Net10/App.xaml.cs index 8ca54a6ce..946904461 100644 --- a/Tests/CrossFramework.WPF.Net48/App.xaml.cs +++ b/Tests/CrossFramework/WPF/Net10/App.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Windows; +using CrossFramework.SelfTest; namespace CrossFramework.WPF { public partial class App : Application { @@ -12,7 +13,15 @@ protected override void OnStartup(StartupEventArgs e) { Console.WriteLine("Content: " + window.GetStatusText()); Console.WriteLine("END"); Shutdown(42); + return; } + + if (e.Args.Length > 0 && (e.Args[0] == "--selftest" || e.Args[0] == "--selftest-crash")) { + WpfSelfTestHost.Run(this, new MainWindow(), induceCrash: e.Args[0] == "--selftest-crash"); + return; + } + + new MainWindow().Show(); } } } diff --git a/Tests/CrossFramework.WPF.Net10/CrossFramework.WPF.Net10.csproj b/Tests/CrossFramework/WPF/Net10/CrossFramework.WPF.Net10.csproj similarity index 67% rename from Tests/CrossFramework.WPF.Net10/CrossFramework.WPF.Net10.csproj rename to Tests/CrossFramework/WPF/Net10/CrossFramework.WPF.Net10.csproj index 37a5b6c03..222cf2b30 100644 --- a/Tests/CrossFramework.WPF.Net10/CrossFramework.WPF.Net10.csproj +++ b/Tests/CrossFramework/WPF/Net10/CrossFramework.WPF.Net10.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WPF.Net10/MainWindow.xaml b/Tests/CrossFramework/WPF/Net10/MainWindow.xaml similarity index 100% rename from Tests/CrossFramework.WPF.Net10/MainWindow.xaml rename to Tests/CrossFramework/WPF/Net10/MainWindow.xaml diff --git a/Tests/CrossFramework.WPF.Net10/MainWindow.xaml.cs b/Tests/CrossFramework/WPF/Net10/MainWindow.xaml.cs similarity index 100% rename from Tests/CrossFramework.WPF.Net10/MainWindow.xaml.cs rename to Tests/CrossFramework/WPF/Net10/MainWindow.xaml.cs diff --git a/Tests/CrossFramework.WPF.Net35/CrossFramework.WPF.Net35.csproj b/Tests/CrossFramework/WPF/Net35/CrossFramework.WPF.Net35.csproj similarity index 85% rename from Tests/CrossFramework.WPF.Net35/CrossFramework.WPF.Net35.csproj rename to Tests/CrossFramework/WPF/Net35/CrossFramework.WPF.Net35.csproj index 8af929669..30f81f7c1 100644 --- a/Tests/CrossFramework.WPF.Net35/CrossFramework.WPF.Net35.csproj +++ b/Tests/CrossFramework/WPF/Net35/CrossFramework.WPF.Net35.csproj @@ -10,5 +10,6 @@ + diff --git a/Tests/CrossFramework.WPF.Net35/Program.cs b/Tests/CrossFramework/WPF/Net35/Program.cs similarity index 79% rename from Tests/CrossFramework.WPF.Net35/Program.cs rename to Tests/CrossFramework/WPF/Net35/Program.cs index 7f8b185dc..3c344630c 100644 --- a/Tests/CrossFramework.WPF.Net35/Program.cs +++ b/Tests/CrossFramework/WPF/Net35/Program.cs @@ -38,6 +38,12 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) { + var selfTestApp = new Application(); + CrossFramework.SelfTest.WpfSelfTestHost.Run(selfTestApp, new MainWindow(), induceCrash: args[0] == "--selftest-crash"); + return selfTestApp.Run(); + } + var app = new Application(); app.Run(new MainWindow()); return 0; diff --git a/Tests/CrossFramework.WPF.Net40/CrossFramework.WPF.Net40.csproj b/Tests/CrossFramework/WPF/Net40/CrossFramework.WPF.Net40.csproj similarity index 85% rename from Tests/CrossFramework.WPF.Net40/CrossFramework.WPF.Net40.csproj rename to Tests/CrossFramework/WPF/Net40/CrossFramework.WPF.Net40.csproj index 27f404649..6079b9223 100644 --- a/Tests/CrossFramework.WPF.Net40/CrossFramework.WPF.Net40.csproj +++ b/Tests/CrossFramework/WPF/Net40/CrossFramework.WPF.Net40.csproj @@ -10,5 +10,6 @@ + diff --git a/Tests/CrossFramework.WPF.Net40/Program.cs b/Tests/CrossFramework/WPF/Net40/Program.cs similarity index 79% rename from Tests/CrossFramework.WPF.Net40/Program.cs rename to Tests/CrossFramework/WPF/Net40/Program.cs index 35a9e55e7..4d81a4378 100644 --- a/Tests/CrossFramework.WPF.Net40/Program.cs +++ b/Tests/CrossFramework/WPF/Net40/Program.cs @@ -38,6 +38,12 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) { + var selfTestApp = new Application(); + CrossFramework.SelfTest.WpfSelfTestHost.Run(selfTestApp, new MainWindow(), induceCrash: args[0] == "--selftest-crash"); + return selfTestApp.Run(); + } + var app = new Application(); app.Run(new MainWindow()); return 0; diff --git a/Tests/CrossFramework.WPF.Net48/App.xaml b/Tests/CrossFramework/WPF/Net48/App.xaml similarity index 81% rename from Tests/CrossFramework.WPF.Net48/App.xaml rename to Tests/CrossFramework/WPF/Net48/App.xaml index a13360fb3..26cf87f1d 100644 --- a/Tests/CrossFramework.WPF.Net48/App.xaml +++ b/Tests/CrossFramework/WPF/Net48/App.xaml @@ -1,5 +1,4 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/Tests/CrossFramework.WPF.Net10/App.xaml.cs b/Tests/CrossFramework/WPF/Net48/App.xaml.cs similarity index 64% rename from Tests/CrossFramework.WPF.Net10/App.xaml.cs rename to Tests/CrossFramework/WPF/Net48/App.xaml.cs index 8ca54a6ce..367a0aa23 100644 --- a/Tests/CrossFramework.WPF.Net10/App.xaml.cs +++ b/Tests/CrossFramework/WPF/Net48/App.xaml.cs @@ -12,7 +12,15 @@ protected override void OnStartup(StartupEventArgs e) { Console.WriteLine("Content: " + window.GetStatusText()); Console.WriteLine("END"); Shutdown(42); + return; } + + if (e.Args.Length > 0 && (e.Args[0] == "--selftest" || e.Args[0] == "--selftest-crash")) { + CrossFramework.SelfTest.WpfSelfTestHost.Run(this, new MainWindow(), induceCrash: e.Args[0] == "--selftest-crash"); + return; + } + + new MainWindow().Show(); } } } diff --git a/Tests/CrossFramework.WPF.Net48/CrossFramework.WPF.Net48.csproj b/Tests/CrossFramework/WPF/Net48/CrossFramework.WPF.Net48.csproj similarity index 65% rename from Tests/CrossFramework.WPF.Net48/CrossFramework.WPF.Net48.csproj rename to Tests/CrossFramework/WPF/Net48/CrossFramework.WPF.Net48.csproj index fdeaebcc1..dc35de7d8 100644 --- a/Tests/CrossFramework.WPF.Net48/CrossFramework.WPF.Net48.csproj +++ b/Tests/CrossFramework/WPF/Net48/CrossFramework.WPF.Net48.csproj @@ -5,4 +5,7 @@ true 7.3 + + + diff --git a/Tests/CrossFramework.WPF.Net48/MainWindow.xaml b/Tests/CrossFramework/WPF/Net48/MainWindow.xaml similarity index 100% rename from Tests/CrossFramework.WPF.Net48/MainWindow.xaml rename to Tests/CrossFramework/WPF/Net48/MainWindow.xaml diff --git a/Tests/CrossFramework.WPF.Net48/MainWindow.xaml.cs b/Tests/CrossFramework/WPF/Net48/MainWindow.xaml.cs similarity index 100% rename from Tests/CrossFramework.WPF.Net48/MainWindow.xaml.cs rename to Tests/CrossFramework/WPF/Net48/MainWindow.xaml.cs diff --git a/Tests/CrossFramework.WPF.Net6/App.xaml b/Tests/CrossFramework/WPF/Net6/App.xaml similarity index 81% rename from Tests/CrossFramework.WPF.Net6/App.xaml rename to Tests/CrossFramework/WPF/Net6/App.xaml index a13360fb3..26cf87f1d 100644 --- a/Tests/CrossFramework.WPF.Net6/App.xaml +++ b/Tests/CrossFramework/WPF/Net6/App.xaml @@ -1,5 +1,4 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/Tests/CrossFramework.WPF.Net6/App.xaml.cs b/Tests/CrossFramework/WPF/Net6/App.xaml.cs similarity index 64% rename from Tests/CrossFramework.WPF.Net6/App.xaml.cs rename to Tests/CrossFramework/WPF/Net6/App.xaml.cs index 8ca54a6ce..367a0aa23 100644 --- a/Tests/CrossFramework.WPF.Net6/App.xaml.cs +++ b/Tests/CrossFramework/WPF/Net6/App.xaml.cs @@ -12,7 +12,15 @@ protected override void OnStartup(StartupEventArgs e) { Console.WriteLine("Content: " + window.GetStatusText()); Console.WriteLine("END"); Shutdown(42); + return; } + + if (e.Args.Length > 0 && (e.Args[0] == "--selftest" || e.Args[0] == "--selftest-crash")) { + CrossFramework.SelfTest.WpfSelfTestHost.Run(this, new MainWindow(), induceCrash: e.Args[0] == "--selftest-crash"); + return; + } + + new MainWindow().Show(); } } } diff --git a/Tests/CrossFramework.WPF.Net6/CrossFramework.WPF.Net6.csproj b/Tests/CrossFramework/WPF/Net6/CrossFramework.WPF.Net6.csproj similarity index 67% rename from Tests/CrossFramework.WPF.Net6/CrossFramework.WPF.Net6.csproj rename to Tests/CrossFramework/WPF/Net6/CrossFramework.WPF.Net6.csproj index f4429b158..e098c3975 100644 --- a/Tests/CrossFramework.WPF.Net6/CrossFramework.WPF.Net6.csproj +++ b/Tests/CrossFramework/WPF/Net6/CrossFramework.WPF.Net6.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WPF.Net6/MainWindow.xaml b/Tests/CrossFramework/WPF/Net6/MainWindow.xaml similarity index 100% rename from Tests/CrossFramework.WPF.Net6/MainWindow.xaml rename to Tests/CrossFramework/WPF/Net6/MainWindow.xaml diff --git a/Tests/CrossFramework.WPF.Net6/MainWindow.xaml.cs b/Tests/CrossFramework/WPF/Net6/MainWindow.xaml.cs similarity index 100% rename from Tests/CrossFramework.WPF.Net6/MainWindow.xaml.cs rename to Tests/CrossFramework/WPF/Net6/MainWindow.xaml.cs diff --git a/Tests/CrossFramework.WPF.Net8/App.xaml b/Tests/CrossFramework/WPF/Net8/App.xaml similarity index 81% rename from Tests/CrossFramework.WPF.Net8/App.xaml rename to Tests/CrossFramework/WPF/Net8/App.xaml index a13360fb3..26cf87f1d 100644 --- a/Tests/CrossFramework.WPF.Net8/App.xaml +++ b/Tests/CrossFramework/WPF/Net8/App.xaml @@ -1,5 +1,4 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/Tests/CrossFramework.WPF.Net8/App.xaml.cs b/Tests/CrossFramework/WPF/Net8/App.xaml.cs similarity index 64% rename from Tests/CrossFramework.WPF.Net8/App.xaml.cs rename to Tests/CrossFramework/WPF/Net8/App.xaml.cs index 8ca54a6ce..367a0aa23 100644 --- a/Tests/CrossFramework.WPF.Net8/App.xaml.cs +++ b/Tests/CrossFramework/WPF/Net8/App.xaml.cs @@ -12,7 +12,15 @@ protected override void OnStartup(StartupEventArgs e) { Console.WriteLine("Content: " + window.GetStatusText()); Console.WriteLine("END"); Shutdown(42); + return; } + + if (e.Args.Length > 0 && (e.Args[0] == "--selftest" || e.Args[0] == "--selftest-crash")) { + CrossFramework.SelfTest.WpfSelfTestHost.Run(this, new MainWindow(), induceCrash: e.Args[0] == "--selftest-crash"); + return; + } + + new MainWindow().Show(); } } } diff --git a/Tests/CrossFramework.WPF.Net8/CrossFramework.WPF.Net8.csproj b/Tests/CrossFramework/WPF/Net8/CrossFramework.WPF.Net8.csproj similarity index 67% rename from Tests/CrossFramework.WPF.Net8/CrossFramework.WPF.Net8.csproj rename to Tests/CrossFramework/WPF/Net8/CrossFramework.WPF.Net8.csproj index 8e794faa1..5ec018f86 100644 --- a/Tests/CrossFramework.WPF.Net8/CrossFramework.WPF.Net8.csproj +++ b/Tests/CrossFramework/WPF/Net8/CrossFramework.WPF.Net8.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WPF.Net8/MainWindow.xaml b/Tests/CrossFramework/WPF/Net8/MainWindow.xaml similarity index 100% rename from Tests/CrossFramework.WPF.Net8/MainWindow.xaml rename to Tests/CrossFramework/WPF/Net8/MainWindow.xaml diff --git a/Tests/CrossFramework.WPF.Net8/MainWindow.xaml.cs b/Tests/CrossFramework/WPF/Net8/MainWindow.xaml.cs similarity index 100% rename from Tests/CrossFramework.WPF.Net8/MainWindow.xaml.cs rename to Tests/CrossFramework/WPF/Net8/MainWindow.xaml.cs diff --git a/Tests/CrossFramework.WinForms.Net10/CrossFramework.WinForms.Net10.csproj b/Tests/CrossFramework/WinForms/Net10/CrossFramework.WinForms.Net10.csproj similarity index 67% rename from Tests/CrossFramework.WinForms.Net10/CrossFramework.WinForms.Net10.csproj rename to Tests/CrossFramework/WinForms/Net10/CrossFramework.WinForms.Net10.csproj index 13bb1cf3d..181756823 100644 --- a/Tests/CrossFramework.WinForms.Net10/CrossFramework.WinForms.Net10.csproj +++ b/Tests/CrossFramework/WinForms/Net10/CrossFramework.WinForms.Net10.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WinForms.Net10/MainForm.cs b/Tests/CrossFramework/WinForms/Net10/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net10/MainForm.cs rename to Tests/CrossFramework/WinForms/Net10/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net10/Program.cs b/Tests/CrossFramework/WinForms/Net10/Program.cs similarity index 74% rename from Tests/CrossFramework.WinForms.Net10/Program.cs rename to Tests/CrossFramework/WinForms/Net10/Program.cs index 143e430ee..873eeba5a 100644 --- a/Tests/CrossFramework.WinForms.Net10/Program.cs +++ b/Tests/CrossFramework/WinForms/Net10/Program.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using CrossFramework.SelfTest; namespace CrossFramework.WinForms { static class Program { @@ -15,6 +16,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); diff --git a/Tests/CrossFramework.WinForms.Net35/CrossFramework.WinForms.Net35.csproj b/Tests/CrossFramework/WinForms/Net35/CrossFramework.WinForms.Net35.csproj similarity index 81% rename from Tests/CrossFramework.WinForms.Net35/CrossFramework.WinForms.Net35.csproj rename to Tests/CrossFramework/WinForms/Net35/CrossFramework.WinForms.Net35.csproj index eb16ee086..a64e96ebe 100644 --- a/Tests/CrossFramework.WinForms.Net35/CrossFramework.WinForms.Net35.csproj +++ b/Tests/CrossFramework/WinForms/Net35/CrossFramework.WinForms.Net35.csproj @@ -8,5 +8,6 @@ + diff --git a/Tests/CrossFramework.WinForms.Net35/MainForm.cs b/Tests/CrossFramework/WinForms/Net35/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net35/MainForm.cs rename to Tests/CrossFramework/WinForms/Net35/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net40/Program.cs b/Tests/CrossFramework/WinForms/Net35/Program.cs similarity index 71% rename from Tests/CrossFramework.WinForms.Net40/Program.cs rename to Tests/CrossFramework/WinForms/Net35/Program.cs index ab393762f..2a6dde0c6 100644 --- a/Tests/CrossFramework.WinForms.Net40/Program.cs +++ b/Tests/CrossFramework/WinForms/Net35/Program.cs @@ -15,6 +15,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.EnableVisualStyles(); Application.Run(new MainForm()); return 0; diff --git a/Tests/CrossFramework.WinForms.Net40/CrossFramework.WinForms.Net40.csproj b/Tests/CrossFramework/WinForms/Net40/CrossFramework.WinForms.Net40.csproj similarity index 81% rename from Tests/CrossFramework.WinForms.Net40/CrossFramework.WinForms.Net40.csproj rename to Tests/CrossFramework/WinForms/Net40/CrossFramework.WinForms.Net40.csproj index 91d1159f8..33b68ebb1 100644 --- a/Tests/CrossFramework.WinForms.Net40/CrossFramework.WinForms.Net40.csproj +++ b/Tests/CrossFramework/WinForms/Net40/CrossFramework.WinForms.Net40.csproj @@ -8,5 +8,6 @@ + diff --git a/Tests/CrossFramework.WinForms.Net40/MainForm.cs b/Tests/CrossFramework/WinForms/Net40/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net40/MainForm.cs rename to Tests/CrossFramework/WinForms/Net40/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net35/Program.cs b/Tests/CrossFramework/WinForms/Net40/Program.cs similarity index 71% rename from Tests/CrossFramework.WinForms.Net35/Program.cs rename to Tests/CrossFramework/WinForms/Net40/Program.cs index ab393762f..2a6dde0c6 100644 --- a/Tests/CrossFramework.WinForms.Net35/Program.cs +++ b/Tests/CrossFramework/WinForms/Net40/Program.cs @@ -15,6 +15,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.EnableVisualStyles(); Application.Run(new MainForm()); return 0; diff --git a/Tests/CrossFramework.WinForms.Net48/CrossFramework.WinForms.Net48.csproj b/Tests/CrossFramework/WinForms/Net48/CrossFramework.WinForms.Net48.csproj similarity index 65% rename from Tests/CrossFramework.WinForms.Net48/CrossFramework.WinForms.Net48.csproj rename to Tests/CrossFramework/WinForms/Net48/CrossFramework.WinForms.Net48.csproj index d47231c13..9770d2631 100644 --- a/Tests/CrossFramework.WinForms.Net48/CrossFramework.WinForms.Net48.csproj +++ b/Tests/CrossFramework/WinForms/Net48/CrossFramework.WinForms.Net48.csproj @@ -5,4 +5,7 @@ true 7.3 + + + diff --git a/Tests/CrossFramework.WinForms.Net48/MainForm.cs b/Tests/CrossFramework/WinForms/Net48/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net48/MainForm.cs rename to Tests/CrossFramework/WinForms/Net48/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net6/Program.cs b/Tests/CrossFramework/WinForms/Net48/Program.cs similarity index 75% rename from Tests/CrossFramework.WinForms.Net6/Program.cs rename to Tests/CrossFramework/WinForms/Net48/Program.cs index e438e143c..41ebbbf16 100644 --- a/Tests/CrossFramework.WinForms.Net6/Program.cs +++ b/Tests/CrossFramework/WinForms/Net48/Program.cs @@ -16,6 +16,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); diff --git a/Tests/CrossFramework.WinForms.Net6/CrossFramework.WinForms.Net6.csproj b/Tests/CrossFramework/WinForms/Net6/CrossFramework.WinForms.Net6.csproj similarity index 67% rename from Tests/CrossFramework.WinForms.Net6/CrossFramework.WinForms.Net6.csproj rename to Tests/CrossFramework/WinForms/Net6/CrossFramework.WinForms.Net6.csproj index 43d1defe0..8e9275af0 100644 --- a/Tests/CrossFramework.WinForms.Net6/CrossFramework.WinForms.Net6.csproj +++ b/Tests/CrossFramework/WinForms/Net6/CrossFramework.WinForms.Net6.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WinForms.Net6/MainForm.cs b/Tests/CrossFramework/WinForms/Net6/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net6/MainForm.cs rename to Tests/CrossFramework/WinForms/Net6/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net48/Program.cs b/Tests/CrossFramework/WinForms/Net6/Program.cs similarity index 75% rename from Tests/CrossFramework.WinForms.Net48/Program.cs rename to Tests/CrossFramework/WinForms/Net6/Program.cs index e438e143c..41ebbbf16 100644 --- a/Tests/CrossFramework.WinForms.Net48/Program.cs +++ b/Tests/CrossFramework/WinForms/Net6/Program.cs @@ -16,6 +16,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); diff --git a/Tests/CrossFramework.WinForms.Net8/CrossFramework.WinForms.Net8.csproj b/Tests/CrossFramework/WinForms/Net8/CrossFramework.WinForms.Net8.csproj similarity index 67% rename from Tests/CrossFramework.WinForms.Net8/CrossFramework.WinForms.Net8.csproj rename to Tests/CrossFramework/WinForms/Net8/CrossFramework.WinForms.Net8.csproj index b6d4633b9..36d5932dc 100644 --- a/Tests/CrossFramework.WinForms.Net8/CrossFramework.WinForms.Net8.csproj +++ b/Tests/CrossFramework/WinForms/Net8/CrossFramework.WinForms.Net8.csproj @@ -5,4 +5,7 @@ true disable + + + diff --git a/Tests/CrossFramework.WinForms.Net8/MainForm.cs b/Tests/CrossFramework/WinForms/Net8/MainForm.cs similarity index 100% rename from Tests/CrossFramework.WinForms.Net8/MainForm.cs rename to Tests/CrossFramework/WinForms/Net8/MainForm.cs diff --git a/Tests/CrossFramework.WinForms.Net8/Program.cs b/Tests/CrossFramework/WinForms/Net8/Program.cs similarity index 75% rename from Tests/CrossFramework.WinForms.Net8/Program.cs rename to Tests/CrossFramework/WinForms/Net8/Program.cs index e438e143c..41ebbbf16 100644 --- a/Tests/CrossFramework.WinForms.Net8/Program.cs +++ b/Tests/CrossFramework/WinForms/Net8/Program.cs @@ -16,6 +16,9 @@ static int Main(string[] args) { return 42; } + if (args.Length > 0 && (args[0] == "--selftest" || args[0] == "--selftest-crash")) + return CrossFramework.SelfTest.WinFormsSelfTestHost.Run(() => new MainForm(), induceCrash: args[0] == "--selftest-crash"); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); diff --git a/Tests/Shared/ConsoleSelfTestHost.cs b/Tests/Shared/ConsoleSelfTestHost.cs new file mode 100644 index 000000000..52795a615 --- /dev/null +++ b/Tests/Shared/ConsoleSelfTestHost.cs @@ -0,0 +1,28 @@ +using System; + +namespace CrossFramework.SelfTest { + // Shared across the console subject apps (linked, not duplicated). Runs the app and reports + // failure if anything goes unhandled. Console apps have no window, so "running" simply means + // executing to completion; the crash path proves the unhandled-exception handler reports it. + // + // Contract: prints START / RUN / END and exits 42 on a clean run; on a crash prints + // "CRASH: ..." and exits 1. System.Console is fully qualified because the subject apps live in + // the CrossFramework.Console namespace, which would otherwise shadow it. + internal static class ConsoleSelfTestHost { + public static int Run(bool induceCrash) { + AppDomain.CurrentDomain.UnhandledException += (sender, e) => { + var ex = e.ExceptionObject as Exception; + System.Console.WriteLine("CRASH: [AppDomain] " + (ex != null ? ex.Message : "unknown")); + System.Console.Out.Flush(); + Environment.Exit(1); + }; + + System.Console.WriteLine("START"); + System.Console.WriteLine("RUN"); + if (induceCrash) + throw new InvalidOperationException("Induced self-test crash"); + System.Console.WriteLine("END"); + return 42; + } + } +} diff --git a/Tests/Shared/WinFormsSelfTestHost.cs b/Tests/Shared/WinFormsSelfTestHost.cs new file mode 100644 index 000000000..a8f6049b5 --- /dev/null +++ b/Tests/Shared/WinFormsSelfTestHost.cs @@ -0,0 +1,61 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace CrossFramework.SelfTest { + // Shared across the WinForms subject apps (linked, not duplicated). Actually runs the app: + // shows the real main window (invisible + off-screen), lets the message loop pump briefly, then + // ends the process. Any unhandled exception (UI thread or background) makes it report failure. + // + // Contract: prints START / SHOWN / END and exits 42 on a clean run; on a crash prints + // "CRASH: ..." and exits 1. Termination is driven by a thread-pool watchdog rather than a UI + // timer, so it always ends even if the message loop never closes the window (e.g. under + // obfuscation, where the WinForms Timer proved unreliable). + internal static class WinFormsSelfTestHost { + public static int Run(Func
formFactory, bool induceCrash = false) { + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + Fail("AppDomain", e.ExceptionObject as Exception); + Application.ThreadException += (sender, e) => + Fail("ThreadException", e.Exception); + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + + Console.WriteLine("START"); + + var form = formFactory(); + form.StartPosition = FormStartPosition.Manual; + form.Location = new Point(-32000, -32000); + form.ShowInTaskbar = false; + form.WindowState = FormWindowState.Minimized; + form.Opacity = 0d; + + form.Shown += (sender, e) => { + Console.WriteLine("SHOWN: " + form.Text); + if (induceCrash) + throw new InvalidOperationException("Induced self-test crash"); + }; + + // Guaranteed terminator, independent of the UI message loop: once the window has had a + // moment to come up (and crash if it is going to), end the process cleanly from a + // thread-pool thread. Rooted across Application.Run so it cannot be collected first. + var watchdog = new System.Threading.Timer( + _ => Succeed(), null, 2000, System.Threading.Timeout.Infinite); + + Application.Run(form); + GC.KeepAlive(watchdog); + Succeed(); + return 42; + } + + static void Succeed() { + Console.WriteLine("END"); + Console.Out.Flush(); + Environment.Exit(42); + } + + static void Fail(string source, Exception ex) { + Console.WriteLine("CRASH: [" + source + "] " + (ex != null ? ex.Message : "unknown")); + Console.Out.Flush(); + Environment.Exit(1); + } + } +} diff --git a/Tests/Shared/WpfSelfTestHost.cs b/Tests/Shared/WpfSelfTestHost.cs new file mode 100644 index 000000000..8a02cad41 --- /dev/null +++ b/Tests/Shared/WpfSelfTestHost.cs @@ -0,0 +1,57 @@ +using System; +using System.Windows; + +namespace CrossFramework.SelfTest { + // Shared across the WPF subject apps (linked, not duplicated). Shows the real main window + // (invisible + off-screen) inside the already-running Application, lets it live briefly, then + // ends the process. Any unhandled exception (dispatcher or background) makes it report failure. + // + // Contract: prints START / SHOWN / END and exits 42 on a clean run; on a crash prints + // "CRASH: ..." and exits 1. Termination is driven by a thread-pool watchdog rather than a UI + // timer, so it always ends even if the dispatcher never closes the window. + internal static class WpfSelfTestHost { + public static void Run(Application app, Window window, bool induceCrash = false) { + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + Fail("AppDomain", e.ExceptionObject as Exception); + app.DispatcherUnhandledException += (sender, e) => + Fail("Dispatcher", e.Exception); + + Console.WriteLine("START"); + + window.ShowInTaskbar = false; + window.ShowActivated = false; + window.WindowState = WindowState.Minimized; + window.Opacity = 0d; + window.WindowStartupLocation = WindowStartupLocation.Manual; + window.Left = -32000; + window.Top = -32000; + + window.Loaded += (sender, e) => { + Console.WriteLine("SHOWN: " + window.Title); + if (induceCrash) + throw new InvalidOperationException("Induced self-test crash"); + }; + + // Guaranteed terminator, independent of the dispatcher (see WinForms host). Rooted for + // the app's lifetime by the running Application, which holds this host on its stack. + _watchdog = new System.Threading.Timer( + _ => Succeed(), null, 2000, System.Threading.Timeout.Infinite); + + window.Show(); + } + + static System.Threading.Timer _watchdog; + + static void Succeed() { + Console.WriteLine("END"); + Console.Out.Flush(); + Environment.Exit(42); + } + + static void Fail(string source, Exception ex) { + Console.WriteLine("CRASH: [" + source + "] " + (ex != null ? ex.Message : "unknown")); + Console.Out.Flush(); + Environment.Exit(1); + } + } +}