diff --git a/native/wasm/build.cake b/native/wasm/build.cake index bd5ca828f8..5c119628aa 100644 --- a/native/wasm/build.cake +++ b/native/wasm/build.cake @@ -16,7 +16,6 @@ Task("libSkiaSharp") .WithCriteria(IsRunningOnLinux()) .Does(() => { - GnNinja($"wasm", "SkiaSharp", $"target_os='linux' " + $"target_cpu='wasm' " + @@ -58,7 +57,8 @@ Task("libSkiaSharp") // separate all the .a files into .o files var skiaOut = SKIA_PATH.Combine("out/wasm"); - var mergeDir = skiaOut.Combine("obj/merge"); + var objDir = skiaOut.Combine("obj"); + var mergeDir = objDir.Combine("merge"); EnsureDirectoryExists(mergeDir); CleanDirectories(mergeDir.FullPath); foreach (var file in GetFiles($"{skiaOut}/*.a")) { @@ -68,14 +68,11 @@ Task("libSkiaSharp") }); } - // add the default font - var input = SKIA_PATH.CombineWithFilePath("modules/canvaskit/fonts/NotoMono-Regular.ttf"); - var embed_resources = SKIA_PATH.CombineWithFilePath("tools/embed_resources.py"); - RunProcess(PYTHON_EXE, new ProcessSettings { - Arguments = $"{embed_resources} --name SK_EMBEDDED_FONTS --input {input} --output {input}.cpp --align 4", - WorkingDirectory = SKIA_PATH.FullPath, - }); - RunProcess(CC, $"-std=c++17 -I. {input}.cpp -r -o {mergeDir}/NotoMonoRegularttf.o"); + // add the default fonts + CompileFonts( + ROOT_PATH.CombineWithFilePath("native/wasm/fonts/NotoMono-Regular.ttf").FullPath, + ROOT_PATH.CombineWithFilePath("native/wasm/fonts/DejaVuSans-Bold.ttf").FullPath, + ROOT_PATH.CombineWithFilePath("native/wasm/fonts/DejaVuSans.ttf").FullPath); // merge all the .o files into the final .a file var oFiles = GetFiles($"{mergeDir}/*.o"); @@ -84,6 +81,16 @@ Task("libSkiaSharp") var outDir = OUTPUT_PATH.Combine($"wasm"); EnsureDirectoryExists(outDir); CopyFileToDirectory(a, outDir); + + void CompileFonts(params string[] fonts) + { + var embed_resources = SKIA_PATH.CombineWithFilePath("tools/embed_resources.py"); + RunProcess(PYTHON_EXE, new ProcessSettings { + Arguments = $"{embed_resources} --name SK_EMBEDDED_FONTS --input {string.Join(" ", fonts)} --output {objDir}/embeddedfonts.cpp --align 4", + WorkingDirectory = SKIA_PATH.FullPath, + }); + RunProcess(CC, $"-std=c++17 -I. {objDir}/embeddedfonts.cpp -r -o {mergeDir}/embeddedfonts.o"); + } }); Task("libHarfBuzzSharp") diff --git a/native/wasm/fonts/DejaVuSans-Bold.ttf b/native/wasm/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 0000000000..1a302a04a2 Binary files /dev/null and b/native/wasm/fonts/DejaVuSans-Bold.ttf differ diff --git a/native/wasm/fonts/DejaVuSans.ttf b/native/wasm/fonts/DejaVuSans.ttf new file mode 100644 index 0000000000..725d11748a Binary files /dev/null and b/native/wasm/fonts/DejaVuSans.ttf differ diff --git a/native/wasm/fonts/NotoMono-Regular.ttf b/native/wasm/fonts/NotoMono-Regular.ttf new file mode 100644 index 0000000000..3560a3a0c8 Binary files /dev/null and b/native/wasm/fonts/NotoMono-Regular.ttf differ diff --git a/scripts/azure-templates-bootstrapper.yml b/scripts/azure-templates-bootstrapper.yml index c262b5a714..9d6cda3b28 100644 --- a/scripts/azure-templates-bootstrapper.yml +++ b/scripts/azure-templates-bootstrapper.yml @@ -15,7 +15,7 @@ parameters: condition: succeeded() # whether or not to run this template shouldPublish: true # whether or not to publish the artifacts configuration: $(CONFIGURATION) # the build configuration - buildExternals: '' # the build number to download externals from + buildExternals: '4127355' # the build number to download externals from verbosity: $(VERBOSITY) # the level of verbosity to use when building docker: '' # the Docker image to build and use dockerArgs: '' # any additional arguments to pass to docker build @@ -26,16 +26,16 @@ parameters: installEmsdk: false # whether or not to install the Emscripten SDK jobs: -# - ${{ if and(ne(parameters.buildExternals, ''), startsWith(parameters.name, 'native_')) }}: -# - template: azure-templates-download.yml -# parameters: -# name: ${{ parameters.name }} -# displayName: ${{ parameters.displayName }} -# vmImage: ${{ parameters.vmImage }} -# condition: ${{ parameters.condition }} -# buildExternals: ${{ parameters.buildExternals }} +- ${{ if and(ne(parameters.buildExternals, ''), startsWith(parameters.name, 'native_')) }}: + - template: azure-templates-download.yml + parameters: + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + vmImage: ${{ parameters.vmImage }} + condition: ${{ parameters.condition }} + buildExternals: ${{ parameters.buildExternals }} -# - ${{ if or(eq(parameters.buildExternals, ''), not(startsWith(parameters.name, 'native_'))) }}: +- ${{ if or(eq(parameters.buildExternals, ''), not(startsWith(parameters.name, 'native_'))) }}: - job: ${{ parameters.name }} displayName: ${{ parameters.displayName }} timeoutInMinutes: 120 diff --git a/tests/SkiaSharp.Wasm.Tests.sln b/tests/SkiaSharp.Wasm.Tests.sln index bbb36fef98..86e91bfef8 100644 --- a/tests/SkiaSharp.Wasm.Tests.sln +++ b/tests/SkiaSharp.Wasm.Tests.sln @@ -1,9 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.30212.25 +VisualStudioVersion = 16.0.29102.215 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Wasm.Tests", "SkiaSharp.Wasm.Tests\SkiaSharp.Wasm.Tests.csproj", "{E7D44825-9F38-4ADA-BCF5-EB9420200DE4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp", "..\binding\SkiaSharp\SkiaSharp.csproj", "{3E1B158B-6C3B-4340-9F01-28E77D24F31D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HarfBuzzSharp", "..\binding\HarfBuzzSharp\HarfBuzzSharp.csproj", "{38FFD397-8A5E-421A-8649-24FE463E1DE9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.HarfBuzz", "..\source\SkiaSharp.HarfBuzz\SkiaSharp.HarfBuzz\SkiaSharp.HarfBuzz.csproj", "{0DE402FA-A101-438E-8528-6EA82E0FF803}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Wasm.Tests", "SkiaSharp.Wasm.Tests\SkiaSharp.Wasm.Tests.csproj", "{E7D44825-9F38-4ADA-BCF5-EB9420200DE4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,9 +17,27 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3E1B158B-6C3B-4340-9F01-28E77D24F31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E1B158B-6C3B-4340-9F01-28E77D24F31D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E1B158B-6C3B-4340-9F01-28E77D24F31D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E1B158B-6C3B-4340-9F01-28E77D24F31D}.Release|Any CPU.Build.0 = Release|Any CPU + {38FFD397-8A5E-421A-8649-24FE463E1DE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38FFD397-8A5E-421A-8649-24FE463E1DE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38FFD397-8A5E-421A-8649-24FE463E1DE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38FFD397-8A5E-421A-8649-24FE463E1DE9}.Release|Any CPU.Build.0 = Release|Any CPU + {0DE402FA-A101-438E-8528-6EA82E0FF803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DE402FA-A101-438E-8528-6EA82E0FF803}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DE402FA-A101-438E-8528-6EA82E0FF803}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DE402FA-A101-438E-8528-6EA82E0FF803}.Release|Any CPU.Build.0 = Release|Any CPU {E7D44825-9F38-4ADA-BCF5-EB9420200DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E7D44825-9F38-4ADA-BCF5-EB9420200DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7D44825-9F38-4ADA-BCF5-EB9420200DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7D44825-9F38-4ADA-BCF5-EB9420200DE4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8355B800-A642-459B-8675-B545839DE6C7} + EndGlobalSection EndGlobal diff --git a/tests/SkiaSharp.Wasm.Tests/Program.cs b/tests/SkiaSharp.Wasm.Tests/Program.cs index 16b1acc7d6..744fb48233 100644 --- a/tests/SkiaSharp.Wasm.Tests/Program.cs +++ b/tests/SkiaSharp.Wasm.Tests/Program.cs @@ -1,11 +1,60 @@ -namespace SkiaSharp.Tests +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Xunit; + +namespace SkiaSharp.Tests { public class Program { - public static int Main() + private static readonly Assembly assembly = typeof(Program).Assembly; + + public static int Main(string[] args) { + Console.WriteLine($"args: {string.Join(" ", args)}"); + + // copy resources out of assembly + var prefix = "SkiaSharp.Tests."; + var resources = assembly.GetManifestResourceNames(); + foreach (var resource in resources) + { + if (!resource.StartsWith(prefix)) + continue; + + if (resource.StartsWith(prefix + "fonts.") || resource.StartsWith(prefix + "images.")) + { + var ext = Path.GetExtension(resource); + var fn = Path.GetFileNameWithoutExtension(resource); + var dest = fn.Substring(prefix.Length - 1).Replace(".", "/") + ext; + var dir = Path.GetDirectoryName(dest); + + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + using (var stream = assembly.GetManifestResourceStream(resource)) + using (var file = File.Create(dest)) + stream.CopyTo(file); + + Console.WriteLine($"Saved file to {dest}: {new FileInfo(dest).Length}"); + } + } + + var filters = new XunitFilters + { + ExcludedTraits = + { + { BaseTest.CategoryKey, new List { BaseTest.GpuCategory, BaseTest.MatchCharacterCategory } }, + { BaseTest.PlatformKey, new List { BaseTest.WasmPlatform } }, + }, + ExcludedClasses = + { + "SkiaSharp.Tests.ApiTest", + }, + }; + var testRunner = new ThreadlessXunitTestRunner(); - var result = testRunner.Run(typeof(Program).Assembly.GetName().Name + ".dll", null); + var result = testRunner.Run(assembly.GetName().Name + ".dll", filters); return result ? 1 : 0; } } diff --git a/tests/SkiaSharp.Wasm.Tests/SkiaSharp.Wasm.Tests.csproj b/tests/SkiaSharp.Wasm.Tests/SkiaSharp.Wasm.Tests.csproj index 02f31c1175..5fbfc8c71c 100644 --- a/tests/SkiaSharp.Wasm.Tests/SkiaSharp.Wasm.Tests.csproj +++ b/tests/SkiaSharp.Wasm.Tests/SkiaSharp.Wasm.Tests.csproj @@ -3,18 +3,22 @@ Exe netstandard2.0 + $(DefineConstants);USE_LIBRARY_LOADER SkiaSharp.Tests SkiaSharp.Tests - true true true true - + + true + true + false + portable + true + + - - - @@ -22,5 +26,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/SkiaSharp.Wasm.Tests/Xunit/ThreadlessXunitTestRunner.cs b/tests/SkiaSharp.Wasm.Tests/Xunit/ThreadlessXunitTestRunner.cs index c2bb3b8e45..30242bd0f1 100644 --- a/tests/SkiaSharp.Wasm.Tests/Xunit/ThreadlessXunitTestRunner.cs +++ b/tests/SkiaSharp.Wasm.Tests/Xunit/ThreadlessXunitTestRunner.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; @@ -11,18 +10,12 @@ namespace SkiaSharp.Tests { internal class ThreadlessXunitTestRunner { - public bool Run(string assemblyFileName, IEnumerable excludedTraits) + public bool Run(string assemblyFileName, XunitFilters filters) { WebAssembly.Runtime.InvokeJS($"if (document) document.body.innerHTML = ''"); Log("Starting tests..."); - var filters = new XunitFilters(); - foreach (var trait in excludedTraits ?? Array.Empty()) - { - ParseEqualSeparatedArgument(filters.ExcludedTraits, trait); - } - var configuration = new TestAssemblyConfiguration { ShadowCopy = false, @@ -67,6 +60,7 @@ public bool Run(string assemblyFileName, IEnumerable excludedTraits) var resultsXmlAssembly = new XElement("assembly"); var resultsSink = new DelegatingXmlCreationSink(summarySink, resultsXmlAssembly); + testSink.Execution.TestStartingEvent += args => { Log($"{args.Message.Test.DisplayName}", color: "gray"); }; testSink.Execution.TestPassedEvent += args => { Log($"[PASS] {args.Message.Test.DisplayName}", color: "green"); }; testSink.Execution.TestSkippedEvent += args => { Log($"[SKIP] {args.Message.Test.DisplayName}", color: "orange"); }; testSink.Execution.TestFailedEvent += args => { Log($"[FAIL] {args.Message.Test.DisplayName}{Environment.NewLine}{ExceptionUtility.CombineMessages(args.Message)}{Environment.NewLine}{ExceptionUtility.CombineStackTraces(args.Message)}", color: "red"); }; @@ -107,22 +101,6 @@ private void Log(string contents, string color = null, string id = null) WebAssembly.Runtime.InvokeJS($"if (document) document.body.innerHTML += '
{contents.Replace("\n", "
")}
'"); } - - private void ParseEqualSeparatedArgument(Dictionary> targetDictionary, string argument) - { - var parts = argument.Split('='); - if (parts.Length != 2 || string.IsNullOrEmpty(parts[0]) || string.IsNullOrEmpty(parts[1])) - throw new ArgumentException("Invalid argument value '{argument}'.", nameof(argument)); - - var name = parts[0]; - var value = parts[1]; - - List excludedTraits; - if (targetDictionary.TryGetValue(name, out excludedTraits!)) - excludedTraits.Add(value); - else - targetDictionary[name] = new List { value }; - } } internal class ThreadlessXunitDiscoverer : Xunit.Sdk.XunitTestFrameworkDiscoverer diff --git a/tests/Tests/BaseTest.cs b/tests/Tests/BaseTest.cs index eec800f05c..f2e38d7c90 100644 --- a/tests/Tests/BaseTest.cs +++ b/tests/Tests/BaseTest.cs @@ -5,10 +5,12 @@ namespace SkiaSharp.Tests { public abstract class BaseTest { - protected const string CategoryKey = "Category"; + public const string CategoryKey = "Category"; + public const string GpuCategory = "GPU"; + public const string MatchCharacterCategory = "MatchCharacter"; - protected const string GpuCategory = "GPU"; - protected const string MatchCharacterCategory = "MatchCharacter"; + public const string PlatformKey = "Platform"; + public const string WasmPlatform = "WASM"; protected static bool IsLinux = PlatformConfiguration.IsLinux; protected static bool IsMac = PlatformConfiguration.IsMac; diff --git a/tests/Tests/GRGlInterfaceTest.cs b/tests/Tests/GRGlInterfaceTest.cs index 28929340dc..4f72aad6f2 100644 --- a/tests/Tests/GRGlInterfaceTest.cs +++ b/tests/Tests/GRGlInterfaceTest.cs @@ -7,6 +7,7 @@ namespace SkiaSharp.Tests { public class GRGlInterfaceTest : SKTest { + [Trait(CategoryKey, GpuCategory)] [SkippableFact] public void InterfaceConstructionWithoutContextDoesNotCrash() { diff --git a/tests/Tests/SKBitmapTest.cs b/tests/Tests/SKBitmapTest.cs index 7f47f74626..44273b40b0 100644 --- a/tests/Tests/SKBitmapTest.cs +++ b/tests/Tests/SKBitmapTest.cs @@ -501,6 +501,7 @@ public void AlphaMaskIsApplied() mask.FreeImage(); } + [Trait(PlatformKey, WasmPlatform)] [SkippableTheory] [InlineData(100, 1000)] public static void ImageScalingMultipleThreadsTest(int numThreads, int numIterationsPerThread) diff --git a/tests/Tests/SKCodecTest.cs b/tests/Tests/SKCodecTest.cs index a7ffa59ce7..11b63c6d7c 100644 --- a/tests/Tests/SKCodecTest.cs +++ b/tests/Tests/SKCodecTest.cs @@ -413,6 +413,7 @@ public void CanReadManagedStream() Assert.NotNull(codec); } + [Trait(PlatformKey, WasmPlatform)] [SkippableFact (Skip = "This keeps breaking CI for some reason.")] public async Task DownloadedStream () { diff --git a/tests/Tests/SKObjectTest.cs b/tests/Tests/SKObjectTest.cs index 10d813d3aa..42ccd9443c 100644 --- a/tests/Tests/SKObjectTest.cs +++ b/tests/Tests/SKObjectTest.cs @@ -245,6 +245,7 @@ private static IntPtr broken_native_method() } } + [Trait(PlatformKey, WasmPlatform)] [SkippableTheory] [InlineData(1)] [InlineData(1000)] @@ -321,6 +322,7 @@ protected override void DisposeNative() } } + [Trait(PlatformKey, WasmPlatform)] [SkippableFact] public async Task DelayedConstructionDoesNotCreateInvalidState() { @@ -371,6 +373,7 @@ public async Task DelayedConstructionDoesNotCreateInvalidState() Assert.Same(objFast, objSlow); } + [Trait(PlatformKey, WasmPlatform)] [SkippableFact] public async Task DelayedDestructionDoesNotCreateInvalidState() {