Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Graphics/tests/Graphics.Tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Errors/
26 changes: 23 additions & 3 deletions src/Graphics/tests/Graphics.Tests/Graphics.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(_MauiDotNetTfm)</TargetFramework>
Expand All @@ -10,15 +10,35 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="xunit" Version="$(XunitPackageVersion)"/>
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorPackageVersion)" >
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorPackageVersion)">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.Extended" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\samples\GraphicsTester.Portable\GraphicsTester.Portable.csproj" />
<ProjectReference Include="..\..\src\Graphics.Skia\Graphics.Skia.csproj" />
<ProjectReference Include="..\..\src\Graphics\Graphics.csproj" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="..\..\samples\GraphicsTester.Portable\Resources\**" Link="Resources\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<None Include="TestImages\**" />
<None Update="TestImages\Windows\**" CopyToOutputDirectory="PreserveNewest" Condition="$([MSBuild]::IsOSPlatform('windows'))" />
<None Update="TestImages\Mac\**" CopyToOutputDirectory="PreserveNewest" Condition="$([MSBuild]::IsOSPlatform('osx'))" />
<None Update="TestImages\Linux\**" CopyToOutputDirectory="PreserveNewest" Condition="!$([MSBuild]::IsOSPlatform('windows')) and !$([MSBuild]::IsOSPlatform('osx'))" />
<None Include="Errors\**" CopyToOutputDirectory="Never" />
</ItemGroup>

<ItemGroup>
<Folder Include="Errors\" />
</ItemGroup>

</Project>
59 changes: 59 additions & 0 deletions src/Graphics/tests/Graphics.Tests/ImageAssert.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.IO;
using SkiaSharp;
using SkiaSharp.Extended;
using Xunit;

namespace Microsoft.Maui;

public static class ImageAssert
{
private const double ImageErrorThreshold = 0.0027;

public static void Equivalent(Stream actualData, string expectedFilename, string diffDirectory, double threshold = ImageErrorThreshold)
{
using var actual = SKImage.FromEncodedData(actualData);
Equivalent(actual, expectedFilename, diffDirectory, threshold);
}

public static void Equivalent(string actualFilename, string expectedFilename, string diffDirectory, double threshold = ImageErrorThreshold)
{
using var actual = SKImage.FromEncodedData(actualFilename);
Equivalent(actual, expectedFilename, diffDirectory, threshold);
}

public static void Equivalent(SKImage actual, string expectedFilename, string diffDirectory, double threshold = ImageErrorThreshold)
{
var expected = SKImage.FromEncodedData(expectedFilename);
var similarity = SKPixelComparer.Compare(actual, expected);

var isSimilar = similarity.ErrorPixelPercentage <= threshold;

if (isSimilar)
{
return;
}

var outputFilename = Path.Combine(diffDirectory, expectedFilename);

var diffFilename = Path.ChangeExtension(outputFilename, ".diff.png");

Directory.CreateDirectory(Path.GetDirectoryName(diffFilename));

using (var diff = SKPixelComparer.GenerateDifferenceMask(actual, expected))
using (var diffData = diff.Encode(SKEncodedImageFormat.Png, 100))
using (var diffFile = File.Create(diffFilename))
{
diffData.SaveTo(diffFile);
}

using (var actualData = actual.Encode(SKEncodedImageFormat.Png, 100))
using (var actualFile = File.Create(Path.ChangeExtension(outputFilename, ".png")))
{
actualData.SaveTo(actualFile);
}

File.Copy(expectedFilename, Path.ChangeExtension(outputFilename, ".expected.png"), true);

Assert.Fail($"Image was not equal. Error was {similarity.ErrorPixelPercentage}% ({similarity.AbsoluteError} pixels). See {diffFilename}");
}
}
97 changes: 97 additions & 0 deletions src/Graphics/tests/Graphics.Tests/SkiaSharpScenarioTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using GraphicsTester.Scenarios;
using Microsoft.Maui.Graphics.Skia;
using Xunit;

namespace Microsoft.Maui.Graphics.Tests;

public class SkiaSharpScenarioTests
{
public static TheoryData<string> Scenarios =>
new(ScenarioList.Scenarios.Select(s => s.ToString()));

[Theory]
[MemberData(nameof(Scenarios))]
public void Scenario(string scenarioName)
{
// find scenario
var scenario = ScenarioList.Scenarios.Single(s => s.ToString() == scenarioName);

// render scenario
using var bmp = new SkiaBitmapExportContext((int)scenario.Width, (int)scenario.Height, 1f);
bmp.Canvas.FillColor = Colors.Transparent;
bmp.Canvas.FillRectangle(0, 0, scenario.Width, scenario.Height);
scenario.Draw(bmp.Canvas);

var expectedImagePath = GetExpectedImageaPath(scenario);

// save image if it did not exist, then fail the test
if (!File.Exists(expectedImagePath))
{
var newImageFilename = Path.Combine(ProjectRoot, expectedImagePath);
Directory.CreateDirectory(Path.GetDirectoryName(newImageFilename));

bmp.WriteToFile(newImageFilename);

Assert.Fail($"Image file did not exist, created: {newImageFilename}");
}

// file existed, compare
ImageAssert.Equivalent(bmp.SKImage, expectedImagePath, GetErrorsImageDirectory(), 0);
}

private static string ProjectRoot =>
Path.GetFullPath("../../../../../src/Graphics/tests/Graphics.Tests");

private static string GetExpectedImageaPath(AbstractScenario scenario)
{
var fileName = GetSafeFilename(scenario.ToString()) + ".png";
var os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "Windows"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
? "Mac"
: "Linux";
var filePath = Path.Combine("TestImages", os, fileName);

return filePath;
}

private static string GetErrorsImageDirectory()
{
var outputFolder = Path.GetFullPath(Path.Combine(ProjectRoot, "Errors"));

if (Environment.GetEnvironmentVariable("BUILD_ARTIFACTSTAGINGDIRECTORY") is { } asd && !string.IsNullOrWhiteSpace(asd))
{
outputFolder = Path.GetFullPath(Path.Combine(
asd, typeof(SkiaSharpScenarioTests).FullName, "Errors"));
}

Directory.CreateDirectory(outputFolder);

return outputFolder;
}

private static string GetSafeFilename(string text)
{
char[] chars = text.ToCharArray();

char[] allowedSpecialChars = ['_', '-'];
for (int i = 0; i < chars.Length; i++)
{
chars[i] = char.IsLetterOrDigit(chars[i]) || allowedSpecialChars.Contains(chars[i])
? char.ToLowerInvariant(chars[i])
: '-';
}

var safe = new string(chars);
while (safe.Contains("--", StringComparison.OrdinalIgnoreCase))
{
safe = safe.Replace("--", "-", StringComparison.OrdinalIgnoreCase);
}

return safe.Trim('-');
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/SingleProject/Resizetizer/test/UnitTests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
errors/
60 changes: 20 additions & 40 deletions src/SingleProject/Resizetizer/test/UnitTests/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using SkiaSharp;
using SkiaSharp.Extended;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
Expand All @@ -18,28 +19,28 @@ public class BaseTest : IDisposable
private readonly string DeleteDirectory;
protected readonly string DestinationDirectory;

public BaseTest(string testContextDirectory = null)
public BaseTest(ITestOutputHelper output)
{
if (!string.IsNullOrEmpty(testContextDirectory))
{
DestinationDirectory = testContextDirectory;
DeleteDirectory = testContextDirectory;
}
else
{
var name = GetType().FullName;
if (name.StartsWith(TestFolderName + ".", StringComparison.OrdinalIgnoreCase))
name = name.Substring(TestFolderName.Length + 1);
Output = output;

var dir = Path.Combine(Path.GetTempPath(), TestFolderName, name, Path.GetRandomFileName());
var name = GetType().FullName;
if (name.StartsWith(TestFolderName + ".", StringComparison.OrdinalIgnoreCase))
name = name.Substring(TestFolderName.Length + 1);

DestinationDirectory = dir;
DeleteDirectory = dir;
}
var dir = Path.Combine(Path.GetTempPath(), TestFolderName, name, Path.GetRandomFileName());

DestinationDirectory = dir;
DeleteDirectory = dir;

Output.WriteLine($"Using DestinationDirectory={DestinationDirectory}");
}

public ITestOutputHelper Output { get; }

public virtual void Dispose()
{
Output.WriteLine($"Cleaning up directories={DeleteDirectory}");

if (Directory.Exists(DeleteDirectory))
Directory.Delete(DeleteDirectory, true);
}
Expand Down Expand Up @@ -137,33 +138,12 @@ void AssertFileMatchesReal(string actualFilename, object[] args = null, [CallerM
}
else
{
using var actual = SKImage.FromEncodedData(actualFilename);
using var expected = SKImage.FromEncodedData(expectedFilename);

var similarity = SKPixelComparer.Compare(actual, expected);

var isSimilar = similarity.ErrorPixelPercentage <= ImageErrorThreshold;

if (!isSimilar)
{
var root = GetTestProjectRoot();

var maskFilename = Path.Combine(root, "errors", expectedFilename);
maskFilename = Path.ChangeExtension(maskFilename, ".mask.png");

Directory.CreateDirectory(Path.GetDirectoryName(maskFilename));
var root = GetTestProjectRoot();
var diffDir = Path.Combine(root, "errors");

using (var mask = SKPixelComparer.GenerateDifferenceMask(actual, expected))
using (var data = mask.Encode(SKEncodedImageFormat.Png, 100))
using (var maskFile = File.Create(maskFilename))
{
data.SaveTo(maskFile);
}
Output.WriteLine($"Validating image similarity with diff dir:{diffDir}");

Assert.True(
isSimilar,
$"Image was not equal. Error was {similarity.ErrorPixelPercentage}% ({similarity.AbsoluteError} pixels). See {maskFilename}");
}
ImageAssert.Equivalent(actualFilename, expectedFilename, diffDir);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
using Microsoft.Build.Utilities;
using SkiaSharp;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
public class DetectInvalidResourceOutputFilenamesTests
{
public class ExecuteForApp : MSBuildTaskTestFixture<DetectInvalidResourceOutputFilenamesTask>
{
public ExecuteForApp(ITestOutputHelper output)
: base(output)
{
}

protected DetectInvalidResourceOutputFilenamesTask GetNewTask(params ITaskItem[] items) =>
new DetectInvalidResourceOutputFilenamesTask
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
public class GeneratePackageAppxManifestTests : MSBuildTaskTestFixture<GeneratePackageAppxManifest>
{
public GeneratePackageAppxManifestTests(ITestOutputHelper output)
: base(output)
{
}

protected GeneratePackageAppxManifest GetNewTask(
string manifest,
string? generatedFilename = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
Expand All @@ -17,7 +18,8 @@ public class GenerateSplashAndroidResourcesTests : MSBuildTaskTestFixture<Genera

static readonly Dictionary<string, string> ResizeMetadata = new() { ["Resize"] = "true" };

public GenerateSplashAndroidResourcesTests()
public GenerateSplashAndroidResourcesTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
_colors = Path.Combine(DestinationDirectory, "values", "maui_colors.xml");
_drawable = Path.Combine(DestinationDirectory, "drawable", "maui_splash_image.xml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
using Microsoft.Build.Utilities;
using SkiaSharp;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
public class GenerateSplashAssetsTests : MSBuildTaskTestFixture<GenerateSplashAssets>
{
public GenerateSplashAssetsTests(ITestOutputHelper output)
: base(output)
{
}

protected GenerateSplashAssets GetNewTask(ITaskItem splash) =>
new()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Build.Utilities;
using SkiaSharp;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Maui.Resizetizer.Tests
{
Expand All @@ -15,7 +16,8 @@ public class GenerateSplashStoryboardTests : MSBuildTaskTestFixture<GenerateSpla

readonly string _storyboard;

public GenerateSplashStoryboardTests()
public GenerateSplashStoryboardTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
_storyboard = Path.Combine(DestinationDirectory, "MauiSplash.storyboard");
}
Expand Down
Loading