From f771c0b137f238e7c8240a741518ea61820d449f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:22:57 -0700 Subject: [PATCH 1/3] Add tests for bundles with non-ascii characters --- .../tests/AppHost.Bundle.Tests/AppLaunch.cs | 31 +++++++++ ...elloWorld_\344\270\255\346\226\207.csproj" | 19 +++++ .../LoadNativeLibrary.cs" | 69 +++++++++++++++++++ .../Program.cs" | 61 ++++++++++++++++ .../SelfContained.csproj" | 20 ++++++ .../Bundle/BundlerConsistencyTests.cs | 22 ++++++ 6 files changed, 222 insertions(+) create mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" create mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" create mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" create mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" diff --git a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs index c1ccb708ad6719..6740f160a51e56 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs @@ -82,6 +82,34 @@ private void RunApp(bool selfContained) } } + [Fact] + private void NonAsciiCharacterSelfContainedApp() + { + // Bundle to a single-file + bool selfContained = false; + string singleFile = sharedTestState.SpecialCharacterSelfContainedApp.Bundle(); + + // Run the bundled app + RunTheApp(singleFile, selfContained); + + if (OperatingSystem.IsMacOS()) + { + string fatApp = MakeUniversalBinary(singleFile, RuntimeInformation.OSArchitecture); + + // Run the fat app + RunTheApp(fatApp, selfContained); + } + + if (OperatingSystem.IsWindows()) + { + // StandaloneApp sets FileVersion to NETCoreApp version. On Windows, this should be copied to singlefilehost resources. + string expectedVersion = TestContext.MicrosoftNETCoreAppVersion.Contains('-') + ? TestContext.MicrosoftNETCoreAppVersion[..TestContext.MicrosoftNETCoreAppVersion.IndexOf('-')] + : TestContext.MicrosoftNETCoreAppVersion; + Assert.Equal(expectedVersion, System.Diagnostics.FileVersionInfo.GetVersionInfo(singleFile).FileVersion); + } + } + [ConditionalTheory(typeof(Binaries.CetCompat), nameof(Binaries.CetCompat.IsSupported))] [InlineData(true)] [InlineData(false)] @@ -186,17 +214,20 @@ public class SharedTestState : IDisposable { public SingleFileTestApp FrameworkDependentApp { get; } public SingleFileTestApp SelfContainedApp { get; } + public SingleFileTestApp SpecialCharacterSelfContainedApp { get; } public SharedTestState() { FrameworkDependentApp = SingleFileTestApp.CreateFrameworkDependent("HelloWorld"); SelfContainedApp = SingleFileTestApp.CreateSelfContained("HelloWorld"); + SpecialCharacterSelfContainedApp = SingleFileTestApp.CreateSelfContained("HelloWorld_中文"); } public void Dispose() { FrameworkDependentApp.Dispose(); SelfContainedApp.Dispose(); + SpecialCharacterSelfContainedApp.Dispose(); } } } diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" new file mode 100644 index 00000000000000..a600aa229c5ec8 --- /dev/null +++ "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" @@ -0,0 +1,19 @@ + + + + $(NetCoreAppCurrent) + Exe + + + + + + $(ProductVersion) + + + + + + + + diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" new file mode 100644 index 00000000000000..afff4786de8f7c --- /dev/null +++ "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace HelloWorld +{ + public static class LoadNativeLibrary + { + private const string LibraryName = "mockhostpolicy"; + private const string LibraryName_AssemblyDirectory = $"{LibraryName}-{nameof(DllImportSearchPath.AssemblyDirectory)}"; + private const string LibraryName_System32 = $"{LibraryName}-{nameof(DllImportSearchPath.System32)}"; + + public static void PInvoke(DllImportSearchPath? flags) + { + try + { + switch (flags) + { + case DllImportSearchPath.AssemblyDirectory: + corehost_unload_assembly_directory(); + break; + case DllImportSearchPath.System32: + corehost_unload_system32(); + break; + default: + corehost_unload(); + break; + } + + Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) succeeded"); + } + catch (DllNotFoundException e) + { + Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) failed: {e}"); + } + } + + public static void UseAPI(DllImportSearchPath? flags) + { + string name = GetLibraryName(flags); + bool success = NativeLibrary.TryLoad(name, typeof(LoadNativeLibrary).Assembly, flags, out _); + Console.WriteLine($"Loading {name} via NativeLibrary API (flags: {(flags.HasValue ? flags : "default")}) {(success ? "succeeded" : "failed")}"); + } + + private static string GetLibraryName(DllImportSearchPath? flags) + { + string name = flags switch + { + DllImportSearchPath.AssemblyDirectory => LibraryName_AssemblyDirectory, + DllImportSearchPath.System32 => LibraryName_System32, + _ => LibraryName + }; + return OperatingSystem.IsWindows() ? name : $"lib{name}"; + } + + [DllImport(LibraryName)] + private static extern int corehost_unload(); + + [DllImport(LibraryName_AssemblyDirectory, EntryPoint = nameof(corehost_unload))] + [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] + private static extern int corehost_unload_assembly_directory(); + + [DllImport(LibraryName_System32, EntryPoint = nameof(corehost_unload))] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int corehost_unload_system32(); + } +} diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" new file mode 100644 index 00000000000000..60bb05c8b641cf --- /dev/null +++ "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace HelloWorld +{ + public static class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + Console.WriteLine(string.Join(Environment.NewLine, args)); + Console.WriteLine(RuntimeInformation.FrameworkDescription); + + if (args.Length == 0) + return; + + switch (args[0]) + { + case "load_native_library_pinvoke": + LoadNativeLibrary.PInvoke(null); + LoadNativeLibrary.PInvoke(DllImportSearchPath.AssemblyDirectory); + LoadNativeLibrary.PInvoke(DllImportSearchPath.System32); + break; + case "load_native_library_api": + LoadNativeLibrary.UseAPI(null); + LoadNativeLibrary.UseAPI(DllImportSearchPath.AssemblyDirectory); + LoadNativeLibrary.UseAPI(DllImportSearchPath.System32); + break; + case "load_shared_library": + var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("SharedLibrary")); + PropertyInfo property = asm.GetType("SharedLibrary.SharedType").GetProperty("Value"); + Console.WriteLine($"SharedLibrary.SharedType.Value = {property.GetValue(null)}"); + break; + case "print_properties": + foreach (string propertyName in args[1..]) + { + var propertyValue = (string)System.AppContext.GetData(propertyName); + if (string.IsNullOrEmpty(propertyValue)) + { + Console.WriteLine($"Property '{propertyName}' was not found."); + continue; + } + + Console.WriteLine($"AppContext.GetData({propertyName}) = {propertyValue}"); + } + break; + case "throw_exception": + // Disable core dumps - test is intentionally crashing + Utilities.CoreDump.Disable(); + throw new Exception("Goodbye World!"); + default: + break; + } + } + } +} diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" new file mode 100644 index 00000000000000..8a216864893b89 --- /dev/null +++ "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" @@ -0,0 +1,20 @@ + + + HelloWorld_中文 + net6.0 + Exe + true + true + false + + + <_SupportedPlatform Condition="'$(TargetsLinux)' == 'true' or '$(TargetsOSX)' == 'true' or '$(TargetsWindows)' == 'true'">true + <_SupportedArchitecture Condition="'$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'x86' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64'">true + $(TargetRid) + + + + + + + diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs index 7cce3c76fdd8bb..a300645399ef51 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs @@ -116,6 +116,25 @@ public void DuplicateBundleRelativePath_Fails() .And.Contain(sharedTestState.App.AppDll); } + [Fact] + public void FilesWithNonAsciiCharsCanBundle() + { + string appPath = sharedTestState.NonAsciiApp.AppDll; + string systemLibPath = sharedTestState.SystemDll; + + // File specification with non-ASCII characters in the relative paths + var fileSpecs = new FileSpec[] + { + new FileSpec(Binaries.AppHost.FilePath, BundlerHostName), + new FileSpec(appPath, "中文/app.dll"), + new FileSpec(appPath, "rel/中文.dll"), + new FileSpec(systemLibPath, "中文") + }; + + Bundler bundler = CreateBundlerInstance(); + var bundlePath = bundler.GenerateBundle(fileSpecs); + } + [Fact] public void CaseSensitiveBundleRelativePath() { @@ -366,11 +385,13 @@ public class SharedTestState : IDisposable { public const string AppName = "HelloWorld"; public TestApp App { get; } + public TestApp NonAsciiApp { get; } public string SystemDll { get; } public SharedTestState() { App = TestApp.CreateFromBuiltAssets(AppName); + NonAsciiApp = TestApp.CreateFromBuiltAssets("HelloWorld_中文"); SystemDll = Path.Combine(TestContext.BuiltDotNet.GreatestVersionSharedFxPath, "System.dll"); } @@ -378,6 +399,7 @@ public SharedTestState() public void Dispose() { App.Dispose(); + NonAsciiApp.Dispose(); } } } From c58471411352af3110137c69df2a120879fbff9a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:28:35 -0700 Subject: [PATCH 2/3] Set selfcontained to true --- src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs index 6740f160a51e56..67179e1577a625 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/AppLaunch.cs @@ -86,7 +86,7 @@ private void RunApp(bool selfContained) private void NonAsciiCharacterSelfContainedApp() { // Bundle to a single-file - bool selfContained = false; + bool selfContained = true; string singleFile = sharedTestState.SpecialCharacterSelfContainedApp.Bundle(); // Run the bundled app From 87afa0721e2ea2e816e4e068a33048402e9aa65d Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:26:59 -0700 Subject: [PATCH 3/3] Just have another project file. Assert bundled files are present. --- ...elloWorld_\344\270\255\346\226\207.csproj" | 0 .../LoadNativeLibrary.cs" | 69 ------------------- .../Program.cs" | 61 ---------------- .../SelfContained.csproj" | 20 ------ .../Bundle/BundlerConsistencyTests.cs | 4 ++ 5 files changed, 4 insertions(+), 150 deletions(-) rename "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" => "src/installer/tests/Assets/Projects/HelloWorld/HelloWorld_\344\270\255\346\226\207.csproj" (100%) delete mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" delete mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" delete mode 100644 "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" "b/src/installer/tests/Assets/Projects/HelloWorld/HelloWorld_\344\270\255\346\226\207.csproj" similarity index 100% rename from "src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/HelloWorld_\344\270\255\346\226\207.csproj" rename to "src/installer/tests/Assets/Projects/HelloWorld/HelloWorld_\344\270\255\346\226\207.csproj" diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" deleted file mode 100644 index afff4786de8f7c..00000000000000 --- "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/LoadNativeLibrary.cs" +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -namespace HelloWorld -{ - public static class LoadNativeLibrary - { - private const string LibraryName = "mockhostpolicy"; - private const string LibraryName_AssemblyDirectory = $"{LibraryName}-{nameof(DllImportSearchPath.AssemblyDirectory)}"; - private const string LibraryName_System32 = $"{LibraryName}-{nameof(DllImportSearchPath.System32)}"; - - public static void PInvoke(DllImportSearchPath? flags) - { - try - { - switch (flags) - { - case DllImportSearchPath.AssemblyDirectory: - corehost_unload_assembly_directory(); - break; - case DllImportSearchPath.System32: - corehost_unload_system32(); - break; - default: - corehost_unload(); - break; - } - - Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) succeeded"); - } - catch (DllNotFoundException e) - { - Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) failed: {e}"); - } - } - - public static void UseAPI(DllImportSearchPath? flags) - { - string name = GetLibraryName(flags); - bool success = NativeLibrary.TryLoad(name, typeof(LoadNativeLibrary).Assembly, flags, out _); - Console.WriteLine($"Loading {name} via NativeLibrary API (flags: {(flags.HasValue ? flags : "default")}) {(success ? "succeeded" : "failed")}"); - } - - private static string GetLibraryName(DllImportSearchPath? flags) - { - string name = flags switch - { - DllImportSearchPath.AssemblyDirectory => LibraryName_AssemblyDirectory, - DllImportSearchPath.System32 => LibraryName_System32, - _ => LibraryName - }; - return OperatingSystem.IsWindows() ? name : $"lib{name}"; - } - - [DllImport(LibraryName)] - private static extern int corehost_unload(); - - [DllImport(LibraryName_AssemblyDirectory, EntryPoint = nameof(corehost_unload))] - [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - private static extern int corehost_unload_assembly_directory(); - - [DllImport(LibraryName_System32, EntryPoint = nameof(corehost_unload))] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern int corehost_unload_system32(); - } -} diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" deleted file mode 100644 index 60bb05c8b641cf..00000000000000 --- "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/Program.cs" +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; - -namespace HelloWorld -{ - public static class Program - { - public static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - Console.WriteLine(string.Join(Environment.NewLine, args)); - Console.WriteLine(RuntimeInformation.FrameworkDescription); - - if (args.Length == 0) - return; - - switch (args[0]) - { - case "load_native_library_pinvoke": - LoadNativeLibrary.PInvoke(null); - LoadNativeLibrary.PInvoke(DllImportSearchPath.AssemblyDirectory); - LoadNativeLibrary.PInvoke(DllImportSearchPath.System32); - break; - case "load_native_library_api": - LoadNativeLibrary.UseAPI(null); - LoadNativeLibrary.UseAPI(DllImportSearchPath.AssemblyDirectory); - LoadNativeLibrary.UseAPI(DllImportSearchPath.System32); - break; - case "load_shared_library": - var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("SharedLibrary")); - PropertyInfo property = asm.GetType("SharedLibrary.SharedType").GetProperty("Value"); - Console.WriteLine($"SharedLibrary.SharedType.Value = {property.GetValue(null)}"); - break; - case "print_properties": - foreach (string propertyName in args[1..]) - { - var propertyValue = (string)System.AppContext.GetData(propertyName); - if (string.IsNullOrEmpty(propertyValue)) - { - Console.WriteLine($"Property '{propertyName}' was not found."); - continue; - } - - Console.WriteLine($"AppContext.GetData({propertyName}) = {propertyValue}"); - } - break; - case "throw_exception": - // Disable core dumps - test is intentionally crashing - Utilities.CoreDump.Disable(); - throw new Exception("Goodbye World!"); - default: - break; - } - } - } -} diff --git "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" "b/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" deleted file mode 100644 index 8a216864893b89..00000000000000 --- "a/src/installer/tests/Assets/Projects/HelloWorld_\344\270\255\346\226\207/SelfContained.csproj" +++ /dev/null @@ -1,20 +0,0 @@ - - - HelloWorld_中文 - net6.0 - Exe - true - true - false - - - <_SupportedPlatform Condition="'$(TargetsLinux)' == 'true' or '$(TargetsOSX)' == 'true' or '$(TargetsWindows)' == 'true'">true - <_SupportedArchitecture Condition="'$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'x86' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64'">true - $(TargetRid) - - - - - - - diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs index a300645399ef51..44cfdfd95358cf 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs @@ -133,6 +133,10 @@ public void FilesWithNonAsciiCharsCanBundle() Bundler bundler = CreateBundlerInstance(); var bundlePath = bundler.GenerateBundle(fileSpecs); + + bundler.BundleManifest.Files.Where(entry => entry.RelativePath.Equals("中文/app.dll")).Single().Type.Should().Be(FileType.Assembly); + bundler.BundleManifest.Files.Where(entry => entry.RelativePath.Equals("rel/中文.dll")).Single().Type.Should().Be(FileType.Assembly); + bundler.BundleManifest.Files.Where(entry => entry.RelativePath.Equals("中文")).Single().Type.Should().Be(FileType.Assembly); } [Fact]