Browse Source

Merge pull request #1089 from SixLabors/af/improve-tests

Harden memory-intensive tests, repurpose Sandbox46
af/octree-no-pixelmap
James Jackson-South 6 years ago
committed by GitHub
parent
commit
f82ddafe07
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      .github/workflows/build-and-test.yml
  2. 2
      .gitignore
  3. 3
      Directory.Build.props
  4. 31
      ImageSharp.sln
  5. 4
      README.md
  6. 15
      ci-build.ps1
  7. 11
      ci-pack.ps1
  8. 2
      tests/Directory.Build.props
  9. 16
      tests/Directory.Build.targets
  10. 24
      tests/ImageSharp.Sandbox46/README.md
  11. 14
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  12. 14
      tests/ImageSharp.Tests.ProfilingSandbox/Program.cs
  13. 2
      tests/ImageSharp.Tests.ProfilingSandbox/README.md
  14. 0
      tests/ImageSharp.Tests.ProfilingSandbox/app.config
  15. 6
      tests/ImageSharp.Tests/AssemblyInfo.cs
  16. 17
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  17. 19
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs
  18. 27
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  19. 1
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  20. 185
      tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs
  21. 90
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs
  22. 20
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  23. 62
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  24. 16
      tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs
  25. 97
      tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs
  26. 29
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs
  27. 69
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs
  28. 7
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  29. 114
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
  30. 5
      tests/ImageSharp.Tests/TestUtilities/TestPixel.cs
  31. 62
      tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs

52
.github/workflows/build-and-test.yml

@ -82,7 +82,7 @@ jobs:
shell: pwsh
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env:
CI : True
CI: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Update Codecov
@ -90,16 +90,56 @@ jobs:
if: matrix.options.codecov == true
with:
token: ${{secrets.CODECOV_TOKEN}}
file: "coverage.${{matrix.options.framework}}.xml"
flags: unittests
- name: Pack # We can use this filter as we know it happens only once and takes the most time to complete.
if: (github.event_name == 'push') && (matrix.options.codecov == true)
Publish:
needs: [Build]
runs-on: windows-latest
if: (github.event_name == 'push')
steps:
- uses: actions/checkout@v2
- name: Install NuGet
uses: NuGet/setup-nuget@v1
- name: Setup Git
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.longpaths true
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Fetch Tags for GitVersion
run: |
git fetch --tags
- name: Fetch master for GitVersion
if: github.ref != 'refs/heads/master'
run: git branch --create-reflog master origin/master
- name: Install GitVersion
uses: gittools/actions/setup-gitversion@v0.3
with:
versionSpec: "5.1.x"
- name: Use GitVersion
id: gitversion # step id used as reference for output values
uses: gittools/actions/execute-gitversion@v0.3
- name: Setup DotNet SDK
uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.101"
- name: Pack
shell: pwsh
run: ./ci-build.ps1 "${{steps.gitversion.outputs.nuGetVersion}}"
run: ./ci-pack.ps1 "${{steps.gitversion.outputs.nuGetVersion}}"
- name: Publish to MyGet
if: (github.event_name == 'push') && (matrix.options.codecov == true)
shell: pwsh
run: nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org

2
.gitignore

@ -216,7 +216,7 @@ artifacts/
*.csproj.bak
#CodeCoverage
/ImageSharp.Coverage.xml
*.lcov
# Tests
**/Images/ActualOutput

3
Directory.Build.props

@ -91,8 +91,9 @@
<RepositoryType>git</RepositoryType>
<RestoreSources>
https://www.myget.org/F/sixlabors/api/v3/index.json;
https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
https://api.nuget.org/v3/index.json;
<!-- Contains RemoteExecutor. Taken from: https://github.com/dotnet/runtime/blob/master/NuGet.config -->
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json;
</RestoreSources>
<SixLaborsPublicKey>002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97</SixLaborsPublicKey>
<UseSharedCompilation>true</UseSharedCompilation>

31
ImageSharp.sln

@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitignore = .gitignore
.gitmodules = .gitmodules
ci-build.ps1 = ci-build.ps1
ci-pack.ps1 = ci-pack.ps1
ci-test.ps1 = ci-test.ps1
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
@ -322,8 +323,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\I
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
@ -331,6 +330,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
@ -380,18 +381,18 @@ Global
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.ActiveCfg = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.Build.0 = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.ActiveCfg = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.Build.0 = Debug|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.Build.0 = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.ActiveCfg = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.Build.0 = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.ActiveCfg = Release|Any CPU
{561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -415,9 +416,9 @@ Global
{E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
{68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}

4
README.md

@ -8,6 +8,7 @@ SixLabors.ImageSharp
<div align="center">
[![Build Status](https://img.shields.io/github/workflow/status/SixLabors/ImageSharp/Build/master)](https://github.com/SixLabors/ImageSharp/actions)
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/LICENSE)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
@ -45,12 +46,13 @@ The **ImageSharp** library is made up of multiple packages:
- Transform methods like Resize, Crop, Skew, Rotate - anything that alters the dimensions of the image
- Non-transform methods like Gaussian Blur, Pixelate, Edge Detection - anything that maintains the original image dimensions
<!--
### Build Status
|Build Status|Code Coverage|
|:----------:|:-----------:|
|[![Build Status](https://img.shields.io/github/workflow/status/SixLabors/ImageSharp/Build/master)](https://github.com/SixLabors/ImageSharp/actions)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
-->
### Questions?
- Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. **Do not** open issues for questions!

15
ci-build.ps1

@ -1,20 +1,13 @@
param(
[Parameter(Mandatory, Position = 0)]
[string]$version,
[Parameter(Mandatory = $false, Position = 1)]
[string]$targetFramework = 'ALL'
[Parameter(Mandatory = $true, Position = 1)]
[string]$targetFramework
)
dotnet clean -c Release
$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY"
if ($targetFramework -ne 'ALL') {
# Building for a specific framework.
dotnet build -c Release -f $targetFramework /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl
}
else {
# Building for packing and publishing.
dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl
}
# Building for a specific framework.
dotnet build -c Release -f $targetFramework /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl

11
ci-pack.ps1

@ -0,0 +1,11 @@
param(
[Parameter(Mandatory, Position = 0)]
[string]$version
)
dotnet clean -c Release
$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY"
# Building for packing and publishing.
dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:packageversion=$version /p:RepositoryUrl=$repositoryUrl

2
tests/Directory.Build.props

@ -24,7 +24,7 @@
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.props" />
<ItemGroup>
<ItemGroup Condition="'$(IsTestProject)' == 'true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" IsImplicitlyDefined="true" />
<PackageReference Include="xunit" IsImplicitlyDefined="true" />
<PackageReference Include="xunit.runner.visualstudio" IsImplicitlyDefined="true" />

16
tests/Directory.Build.targets

@ -17,7 +17,7 @@
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.targets" />
<!-- Tool versions for tool references across all projects -->
<ItemGroup>
<ItemGroup Condition="'$(IsTestProject)' == 'true'">
<!--dotnet tools does not have an x86 runner. You have to use separate SDKs-->
<!--https://github.com/actions/setup-dotnet/issues/72-->
<DotNetCliToolReference Update="dotnet-xunit" Version="2.3.1" />
@ -28,10 +28,12 @@
<PropertyGroup Condition="'$(codecov)' == 'true' AND '$(IsTestProject)' == 'true'">
<CollectCoverage>true</CollectCoverage>
<UseSourceLink>true</UseSourceLink>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
<Include>[SixLabors.*]*</Include>
<CoverletOutputFormat>lcov</CoverletOutputFormat>
<!--Output injects target framework into name despite explicit config. See build yml-->
<CoverletOutput>$(MSBuildThisFileDirectory)..\coverage.xml</CoverletOutput>
<!--Used by coverlet dues to reference issues with SixLabors.Core-->
<CoverletOutputPath>$(MSBuildThisFileDirectory)..\</CoverletOutputPath>
<CoverletOutput>$(CoverletOutputPath)$(AssemblyName).$(TargetFramework).lcov</CoverletOutput>
<!--Used by coverlet dues to reference issues-->
<!--https://github.com/tonerdo/coverlet/blob/master/Documentation/KnowIssues.md#4-failed-to-resolve-assembly-during-instrumentation-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
@ -43,13 +45,13 @@
<PackageReference Update="coverlet.collector" Version="1.1.0" PrivateAssets="All"/>
<PackageReference Update="coverlet.msbuild" Version="2.8.0" PrivateAssets="All"/>
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.14.4" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="5.0.0-beta.20069.1" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Update="Moq" Version="4.10.0" />
<PackageReference Update="Pfim" Version="0.9.1" />
<PackageReference Update="System.Drawing.Common" Version="4.7.0" />
<!--TODO: Fix implicit conversion issues so we can move to 2.4.1-->
<PackageReference Update="xunit" Version="2.3.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Update="xunit" Version="2.4.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>
</Project>

24
tests/ImageSharp.Sandbox46/README.md

@ -1,24 +0,0 @@
## Purpose
This project aims to workaround certain .NET Core tooling issues in Visual Studio based developer workflow at the time of it's creation (January 2017):
- .NET Core Performance profiling is not possible neither with Visual Studio nor with JetBrains profilers
- ~~JetBrains Unit Test explorer does not work with .NET Core projects~~
## How does it work?
- By referencing .NET 4.5 dll-s created by net45 target's of ImageSharp projects. NOTE: These are not project references!
- By including test classes (and utility classes) of the `ImageSharp.Tests` project using MSBUILD `<Link>`
- Compiling `ImageSharp.Sandbox46` should trigger the compilation of ImageSharp subprojects using a manually defined solution dependencies
## How to profile unit tests
#### 1. With Visual Studio 2015 Test Runner
- **Do not** build `ImageSharp.Tests`
- Build `ImageSharp.Sandbox46`
- Use the [context menu in Test Explorer](https://adamprescott.net/2012/12/12/performance-profiling-for-unit-tests/)
NOTE:
There was no *Profile test* option in my VS Professional. Maybe things were messed by VS2017 RC installation. [This post suggests](http://stackoverflow.com/questions/32034375/profiling-tests-in-visual-studio-community-2015) it's necessary to own Premium or Ultimate edition of Visual Studio to profile tests.
#### 2. With JetBrains ReSharper Ultimate
- The `Sandbox46` project is no longer needed here. The classic `ImageSharp.Tests` project can be discovered by Unit Test Explorer.
- You can use [context menus](https://www.jetbrains.com/resharper/features/unit_testing.html) from your test class, or from unit Test Exporer/Unit Test Sessions windows.
![Context Menu](https://www.jetbrains.com/resharper/features/screenshots/100/unit_testing_profiling.png)

14
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj → tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -2,26 +2,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>SixLabors.ImageSharp.Sandbox46</AssemblyName>
<AssemblyName>ImageSharp.Tests.ProfilingSandbox</AssemblyName>
<Description>A cross-platform library for processing of image files written in C#</Description>
<OutputType>Exe</OutputType>
<Prefer32Bit>false</Prefer32Bit>
<RootNamespace>SixLabors.ImageSharp.Sandbox46</RootNamespace>
<RootNamespace>SixLabors.ImageSharp.Tests.ProfilingSandbox</RootNamespace>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
<StartupObject>SixLabors.ImageSharp.Sandbox46.Program</StartupObject>
<StartupObject>SixLabors.ImageSharp.Tests.ProfilingSandbox.Program</StartupObject>
<!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\ImageSharp.Tests\**\*.cs" Link="Tests\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Moq" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ImageSharp.Tests\ImageSharp.Tests.csproj" />
</ItemGroup>
</Project>

14
tests/ImageSharp.Sandbox46/Program.cs → tests/ImageSharp.Tests.ProfilingSandbox/Program.cs

@ -1,18 +1,14 @@
// <copyright file="Program.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System;
using SixLabors.ImageSharp.Tests.Formats.Jpg;
using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations;
using SixLabors.ImageSharp.Tests.ProfilingBenchmarks;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Sandbox46
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
{
using System;
using SixLabors.ImageSharp.Tests.Formats.Jpg;
using Xunit.Abstractions;
public class Program
{
private class ConsoleOutput : ITestOutputHelper

2
tests/ImageSharp.Tests.ProfilingSandbox/README.md

@ -0,0 +1,2 @@
## ImageSharp.Tests.ProfilingSandbox
Helper project to run and profile unit tests or other "sandbox" code from a single .exe entry point.

0
tests/ImageSharp.Sandbox46/app.config → tests/ImageSharp.Tests.ProfilingSandbox/app.config

6
tests/ImageSharp.Tests/AssemblyInfo.cs

@ -0,0 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("ImageSharp.Tests.ProfilingSandbox")]

17
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
// ReSharper disable InconsistentNaming
@ -15,22 +17,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void DecodeBaselineJpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
static void RunTest(string providerDump)
{
// skipping to avoid OutOfMemoryException on CI
return;
}
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(
this.GetImageComparer(provider),
GetImageComparer(provider),
provider,
appendPixelTypeToFileName: false);
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, providerDump).Dispose();
}
[Theory]

19
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
// ReSharper disable InconsistentNaming
@ -16,22 +18,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void DecodeProgressiveJpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
static void RunTest(string providerDump)
{
// skipping to avoid OutOfMemoryException on CI
return;
}
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
image.CompareToReferenceOutput(
this.GetImageComparer(provider),
GetImageComparer(provider),
provider,
appendPixelTypeToFileName: false);
}
string dump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, dump).Dispose();
}
}
}
}

27
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -4,11 +4,12 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
@ -23,9 +24,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector;
private const float BaselineTolerance = 0.001F / 100;
private const float ProgressiveTolerance = 0.2F / 100;
private ImageComparer GetImageComparer<TPixel>(TestImageProvider<TPixel> provider)
static JpegDecoderTests()
{
TestEnvironment.PrepareRemoteExecutor();
}
private static ImageComparer GetImageComparer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string file = provider.SourceFileOrDescription;
@ -88,23 +95,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
{
return;
}
// For 32 bit test environments:
provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
static void RunTest(string providerDump)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false);
}
provider.Configuration.MemoryAllocator.ReleaseRetainedResources();
string dump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, dump).Dispose();
}
// DEBUG ONLY!

1
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -18,6 +18,7 @@
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Microsoft.DotNet.RemoteExecutor" />
<PackageReference Include="Moq" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>

185
tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs

@ -4,39 +4,36 @@
// ReSharper disable InconsistentNaming
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Win32;
using SixLabors.ImageSharp.Tests;
using Xunit;
namespace SixLabors.ImageSharp.Memory.Tests
{
// TODO: Re-enable memory-intensive tests with arcade RemoteExecutor:
// https://github.com/dotnet/runtime/blob/master/docs/project/writing-tests.md#remoteexecutor
public class ArrayPoolMemoryAllocatorTests
{
private const int MaxPooledBufferSizeInBytes = 2048;
private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2;
private MemoryAllocator MemoryAllocator { get; set; } =
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
/// <summary>
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location.
/// Contains SUT for in-process tests.
/// </summary>
private bool CheckIsRentingPooledBuffer<T>(int length)
where T : struct
{
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
buffer.Dispose();
private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture();
buffer = this.MemoryAllocator.Allocate<T>(length);
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference());
buffer.Dispose();
/// <summary>
/// Contains SUT for tests executed by <see cref="RemoteExecutor"/>,
/// recreated in each external process.
/// </summary>
private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture();
return sameBuffers;
static ArrayPoolMemoryAllocatorTests()
{
TestEnvironment.PrepareRemoteExecutor();
}
public class BufferTests : BufferTestSuite
@ -78,21 +75,21 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(MaxPooledBufferSizeInBytes - 1)]
public void SmallBuffersArePooled_OfByte(int size)
{
Assert.True(this.CheckIsRentingPooledBuffer<byte>(size));
Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<byte>(size));
}
[Theory(Skip = "Should be executed from a separate process.")]
[Theory]
[InlineData(128 * 1024 * 1024)]
[InlineData(MaxPooledBufferSizeInBytes + 1)]
public void LargeBuffersAreNotPooled_OfByte(int size)
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest(string sizeStr)
{
// can lead to OutOfMemoryException
return;
int size = int.Parse(sizeStr);
StaticFixture.CheckIsRentingPooledBuffer<byte>(size);
}
Assert.False(this.CheckIsRentingPooledBuffer<byte>(size));
RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose();
}
[Fact]
@ -100,21 +97,15 @@ namespace SixLabors.ImageSharp.Memory.Tests
{
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1;
Assert.True(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(count));
}
[Fact(Skip = "Should be executed from a separate process.")]
[Fact]
public unsafe void LaregeBuffersAreNotPooled_OfBigValueType()
{
if (!TestEnvironment.Is64BitProcess)
{
// can lead to OutOfMemoryException
return;
}
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1;
Assert.False(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(count));
}
[Theory]
@ -122,12 +113,13 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(AllocationOptions.Clean)]
public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options)
{
using (IMemoryOwner<int> firstAlloc = this.MemoryAllocator.Allocate<int>(42))
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
using (IMemoryOwner<int> firstAlloc = memoryAllocator.Allocate<int>(42))
{
firstAlloc.GetSpan().Fill(666);
}
using (IMemoryOwner<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, options))
using (IMemoryOwner<int> secondAlloc = memoryAllocator.Allocate<int>(42, options))
{
int expected = options == AllocationOptions.Clean ? 0 : 666;
Assert.Equal(expected, secondAlloc.GetSpan()[0]);
@ -139,7 +131,8 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(true)]
public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive)
{
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32);
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(32);
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan());
if (!keepBufferAlive)
@ -147,9 +140,9 @@ namespace SixLabors.ImageSharp.Memory.Tests
buffer.Dispose();
}
this.MemoryAllocator.ReleaseRetainedResources();
memoryAllocator.ReleaseRetainedResources();
buffer = this.MemoryAllocator.Allocate<int>(32);
buffer = memoryAllocator.Allocate<int>(32);
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference()));
}
@ -157,87 +150,69 @@ namespace SixLabors.ImageSharp.Memory.Tests
[Fact]
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed()
{
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32);
this.MemoryAllocator.ReleaseRetainedResources();
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(32);
memoryAllocator.ReleaseRetainedResources();
buffer.Dispose();
}
[Fact(Skip = "Should be executed from a separate process.")]
[Fact]
public void AllocationOverLargeArrayThreshold_UsesDifferentPool()
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest()
{
// can lead to OutOfMemoryException
return;
}
const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int);
const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int);
IMemoryOwner<int> small = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1);
ref int ptr2Small = ref small.GetReference();
small.Dispose();
IMemoryOwner<int> small = this.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1);
ref int ptr2Small = ref small.GetReference();
small.Dispose();
IMemoryOwner<int> large = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1);
IMemoryOwner<int> large = this.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1);
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference()));
}
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference()));
RemoteExecutor.Invoke(RunTest).Dispose();
}
[Fact(Skip = "Should be executed from a separate process.")]
[Fact]
public void CreateWithAggressivePooling()
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest()
{
// can lead to OutOfMemoryException
return;
StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(4096 * 4096));
}
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(4096 * 4096));
RemoteExecutor.Invoke(RunTest).Dispose();
}
[Fact(Skip = "Should be executed from a separate process.")]
[Fact]
public void CreateDefault()
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest()
{
// can lead to OutOfMemoryException
return;
}
StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault();
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault();
Assert.False(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2 * 4096 * 4096));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2048 * 2048));
}
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2 * 4096 * 4096));
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
RemoteExecutor.Invoke(RunTest).Dispose();
}
[Fact]
public void CreateWithModeratePooling()
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest()
{
// can lead to OutOfMemoryException
return;
StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
Assert.False(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2048 * 2048));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(1024 * 16));
}
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(1024 * 16));
}
[StructLayout(LayoutKind.Sequential)]
private struct Rgba32
{
private readonly uint dummy;
}
private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5;
[StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)]
private struct LargeStruct
{
RemoteExecutor.Invoke(RunTest).Dispose();
}
[Theory]
@ -245,7 +220,8 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<LargeStruct>(length));
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
Assert.Equal("length", ex.ParamName);
}
@ -253,8 +229,45 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(-1)]
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.AllocateManagedByteBuffer(length));
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length));
Assert.Equal("length", ex.ParamName);
}
private class MemoryAllocatorFixture
{
public MemoryAllocator MemoryAllocator { get; set; } =
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
/// <summary>
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location.
/// </summary>
public bool CheckIsRentingPooledBuffer<T>(int length)
where T : struct
{
IMemoryOwner<T> buffer = MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
buffer.Dispose();
buffer = MemoryAllocator.Allocate<T>(length);
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference());
buffer.Dispose();
return sameBuffers;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct SmallStruct
{
private readonly uint dummy;
}
private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5;
[StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)]
private struct LargeStruct
{
}
}
}

90
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs

@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void NormalBlendFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.NormalSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void NormalBlendFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> MultiplyFunctionData = new TheoryData<object, object, float, object> {
@ -68,8 +68,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void MultiplyFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.MultiplySrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -77,8 +77,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void MultiplyFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> AddFunctionData = new TheoryData<object, object, float, object> {
@ -107,8 +107,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void AddFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.AddSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -116,8 +116,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void AddFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> SubtractFunctionData = new TheoryData<object, object, float, object> {
@ -146,8 +146,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void SubtractFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.SubtractSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void SubtractFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> ScreenFunctionData = new TheoryData<object, object, float, object> {
@ -185,8 +185,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void ScreenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.ScreenSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -194,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void ScreenFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> DarkenFunctionData = new TheoryData<object, object, float, object> {
@ -224,8 +224,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void DarkenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.DarkenSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -233,8 +233,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void DarkenFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> LightenFunctionData = new TheoryData<object, object, float, object> {
@ -263,8 +263,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void LightenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.LightenSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void LightenFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> OverlayFunctionData = new TheoryData<object, object, float, object> {
@ -302,8 +302,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void OverlayFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.OverlaySrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -311,8 +311,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void OverlayFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -322,7 +322,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
public static TheoryData<object, object, float, object> HardLightFunctionData = new TheoryData<object, object, float, object> {
@ -341,8 +341,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void HardLightFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = PorterDuffFunctions.HardLightSrcOver((TPixel)back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -350,8 +350,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
public void HardLightFunctionBlender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : struct, IPixel<TPixel>
{
TPixel actual = new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(back, source, amount);
VectorAssert.Equal(expected, actual, 2);
TPixel actual = new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount);
VectorAssert.Equal(expected.AsPixel(), actual, 2);
}
[Theory]
@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{
var dest = new Span<TPixel>(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected, dest[0], 2);
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
}
}
}

20
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
@ -279,20 +279,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
}
public static readonly TheoryData<IPixel> Generic_To_Data = new TheoryData<IPixel>
public static readonly TheoryData<object> Generic_To_Data = new TheoryData<object>
{
default(Rgba32),
default(Bgra32),
default(Rgb24),
default(L8),
default(L16),
default(Rgb48),
default(Rgba64)
new TestPixel<Rgba32>(),
new TestPixel<Bgra32>(),
new TestPixel<Rgb24>(),
new TestPixel<L8>(),
new TestPixel<L16>(),
new TestPixel<Rgb48>(),
new TestPixel<Rgba64>()
};
[Theory]
[MemberData(nameof(Generic_To_Data))]
public void Generic_To<TDestPixel>(TDestPixel dummy)
public void Generic_To<TDestPixel>(TestPixel<TDestPixel> dummy)
where TDestPixel : struct, IPixel<TDestPixel>
{
const int Count = 2134;

62
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -6,11 +6,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
@ -18,6 +19,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{
public class BokehBlurTest
{
static BokehBlurTest()
{
TestEnvironment.PrepareRemoteExecutor();
}
private static readonly string Components10x2 = @"
[[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j
-0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j
@ -124,10 +130,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public void BokehBlurFilterProcessor<TPixel>(TestImageProvider<TPixel> provider, BokehBlurInfo value)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x => x.BokehBlur(value.Radius, value.Components, value.Gamma),
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
static void RunTest(string providerDump, string infoDump)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
BokehBlurInfo value = BasicSerializer.Deserialize<BokehBlurInfo>(infoDump);
provider.RunValidatingProcessorTest(
x => x.BokehBlur(value.Radius, value.Components, value.Gamma),
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
}
RemoteExecutor
.Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value))
.Dispose();
}
[Theory]
@ -137,9 +155,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public void BokehBlurFilterProcessor_WorksWithAllPixelTypes<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x => x.BokehBlur(8, 2, 3),
appendSourceFileOrDescription: false);
static void RunTest(string providerDump)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
provider.RunValidatingProcessorTest(
x => x.BokehBlur(8, 2, 3),
appendSourceFileOrDescription: false);
}
RemoteExecutor
.Invoke(RunTest, BasicSerializer.Serialize(provider))
.Dispose();
}
@ -148,15 +175,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public void BokehBlurFilterProcessor_Bounded<TPixel>(TestImageProvider<TPixel> provider, BokehBlurInfo value)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x =>
static void RunTest(string providerDump, string infoDump)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
BokehBlurInfo value = BasicSerializer.Deserialize<BokehBlurInfo>(infoDump);
provider.RunValidatingProcessorTest(
x =>
{
Size size = x.GetCurrentSize();
var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2);
x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds);
},
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
}
RemoteExecutor
.Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value))
.Dispose();
}
}
}

16
tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs

@ -31,21 +31,9 @@ namespace SixLabors.ImageSharp.Tests
protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType)
{
MethodInfo m = testMethod.DeclaringType.GetMethod(this.memberMethodName);
Type[] args = factoryType.GetGenericArguments();
Type colorType = args.Single();
Type imgType = typeof(Image<>).MakeGenericType(colorType);
Type funcType = typeof(Func<>).MakeGenericType(imgType);
MethodInfo genericMethod = m.MakeGenericMethod(args);
Delegate d = genericMethod.CreateDelegate(funcType);
return new object[] { d };
return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName};
}
protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda";
}
}
}

97
tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs

@ -0,0 +1,97 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.TestUtilities
{
/// <summary>
/// RemoteExecutor can only execute static methods, which can only consume string arguments,
/// because data is being passed on command line interface. This utility allows serialization
/// of <see cref="IXunitSerializable"/> types to strings.
/// </summary>
internal class BasicSerializer : IXunitSerializationInfo
{
private readonly Dictionary<string, string> map = new Dictionary<string, string>();
public const char Separator = ':';
private string DumpToString(Type type)
{
using var ms = new MemoryStream();
using var writer = new StreamWriter(ms);
writer.WriteLine(type.FullName);
foreach (KeyValuePair<string, string> kv in this.map)
{
writer.WriteLine($"{kv.Key}{Separator}{kv.Value}");
}
writer.Flush();
byte[] data = ms.ToArray();
return System.Convert.ToBase64String(data);
}
private Type LoadDump(string dump)
{
byte[] data = System.Convert.FromBase64String(dump);
using var ms = new MemoryStream(data);
using var reader = new StreamReader(ms);
var type = Type.GetType(reader.ReadLine());
for (string s = reader.ReadLine(); s != null ; s = reader.ReadLine())
{
string[] kv = s.Split(Separator);
this.map[kv[0]] = kv[1];
}
return type;
}
public static string Serialize(IXunitSerializable serializable)
{
var serializer = new BasicSerializer();
serializable.Serialize(serializer);
return serializer.DumpToString(serializable.GetType());
}
public static T Deserialize<T>(string dump) where T : IXunitSerializable
{
var serializer = new BasicSerializer();
Type type = serializer.LoadDump(dump);
var result = (T) Activator.CreateInstance(type);
result.Deserialize(serializer);
return result;
}
public void AddValue(string key, object value, Type type = null)
{
Guard.NotNull(key, nameof(key));
if (value == null)
{
return;
}
type ??= value.GetType();
this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value);
}
public object GetValue(string key, Type type)
{
Guard.NotNull(key, nameof(key));
if (!this.map.TryGetValue(key, out string str))
{
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str);
}
public T GetValue<T>(string key) => (T)this.GetValue(key, typeof(T));
}
}

29
tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs

@ -1,29 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
/// <summary>
/// Provides <see cref="Image{TPixel}" /> instances for parametric unit tests.
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
public abstract partial class TestImageProvider<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private class LambdaProvider : TestImageProvider<TPixel>
{
private readonly Func<Image<TPixel>> factoryFunc;
public LambdaProvider(Func<Image<TPixel>> factoryFunc)
{
this.factoryFunc = factoryFunc;
}
public override Image<TPixel> GetImage() => this.factoryFunc();
}
}
}

69
tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs

@ -0,0 +1,69 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Reflection;
using SixLabors.ImageSharp.PixelFormats;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests
{
/// <summary>
/// Provides <see cref="Image{TPixel}" /> instances for parametric unit tests.
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
public abstract partial class TestImageProvider<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private class MemberMethodProvider : TestImageProvider<TPixel>
{
private string declaringTypeName;
private string methodName;
private Func<Image<TPixel>> factoryFunc;
public MemberMethodProvider()
{
}
public MemberMethodProvider(string declaringTypeName, string methodName)
{
this.declaringTypeName = declaringTypeName;
this.methodName = methodName;
}
public override Image<TPixel> GetImage()
{
this.factoryFunc ??= this.GetFactory();
return this.factoryFunc();
}
public override void Serialize(IXunitSerializationInfo info)
{
base.Serialize(info);
info.AddValue(nameof(this.declaringTypeName), this.declaringTypeName);
info.AddValue(nameof(this.methodName), this.methodName);
}
public override void Deserialize(IXunitSerializationInfo info)
{
base.Deserialize(info);
this.methodName = info.GetValue<string>(nameof(this.methodName));
this.declaringTypeName = info.GetValue<string>(nameof(this.declaringTypeName));
}
private Func<Image<TPixel>> GetFactory()
{
var declaringType = Type.GetType(this.declaringTypeName);
MethodInfo m = declaringType.GetMethod(this.methodName);
Type pixelType = typeof(TPixel);
Type imgType = typeof(Image<>).MakeGenericType(pixelType);
Type funcType = typeof(Func<>).MakeGenericType(imgType);
MethodInfo genericMethod = m.MakeGenericMethod(pixelType);
return (Func<Image<TPixel>>) genericMethod.CreateDelegate(funcType);
}
}
}
}

7
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests
/// Provides <see cref="Image{TPixel}" /> instances for parametric unit tests.
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
public abstract partial class TestImageProvider<TPixel> : ITestImageProvider
public abstract partial class TestImageProvider<TPixel> : ITestImageProvider, IXunitSerializable
where TPixel : struct, IPixel<TPixel>
{
public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType();
@ -73,10 +73,11 @@ namespace SixLabors.ImageSharp.Tests
}
public static TestImageProvider<TPixel> Lambda(
Func<Image<TPixel>> factoryFunc,
string declaringTypeName,
string methodName,
MethodInfo testMethod = null,
PixelTypes pixelTypeOverride = PixelTypes.Undefined)
=> new LambdaProvider(factoryFunc).Init(testMethod, pixelTypeOverride);
=> new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride);
public static TestImageProvider<TPixel> Solid(
int width,

114
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests
{
@ -45,13 +47,12 @@ namespace SixLabors.ImageSharp.Tests
internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value;
private static readonly FileInfo TestAssemblyFile =
new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location);
private static string GetSolutionDirectoryFullPathImpl()
{
string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location;
var assemblyFile = new FileInfo(assemblyLocation);
DirectoryInfo directory = assemblyFile.Directory;
DirectoryInfo directory = TestAssemblyFile.Directory;
while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any())
{
@ -62,20 +63,20 @@ namespace SixLabors.ImageSharp.Tests
catch (Exception ex)
{
throw new Exception(
$"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!",
$"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!",
ex);
}
if (directory == null)
{
throw new Exception($"Unable to find ImageSharp solution directory from {assemblyLocation}!");
throw new Exception($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!");
}
}
return directory.FullName;
}
private static string GetFullPath(string relativePath) =>
private static string GetFullPath(string relativePath) =>
Path.Combine(SolutionDirectoryFullPath, relativePath)
.Replace('\\', Path.DirectorySeparatorChar);
@ -83,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests
/// Gets the correct full path to the Input Images directory.
/// </summary>
internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath);
/// <summary>
/// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.)
/// </summary>
@ -100,13 +101,15 @@ namespace SixLabors.ImageSharp.Tests
actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar);
internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194
internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
internal static bool Is64BitProcess => IntPtr.Size == 8;
internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion);
/// <summary>
/// Creates the image output directory.
/// </summary>
@ -132,6 +135,95 @@ namespace SixLabors.ImageSharp.Tests
return path;
}
/// <summary>
/// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework,
/// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe
/// with the help of CorFlags.exe found in Windows SDK.
/// </summary>
internal static void PrepareRemoteExecutor()
{
if (!IsFramework)
{
return;
}
string remoteExecutorConfigPath =
Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config");
if (File.Exists(remoteExecutorConfigPath))
{
// already prepared
return;
}
string testProjectConfigPath = TestAssemblyFile.FullName + ".config";
File.Copy(testProjectConfigPath, remoteExecutorConfigPath);
if (Is64BitProcess)
{
return;
}
EnsureRemoteExecutorIs32Bit();
}
/// <summary>
/// Locate and run CorFlags.exe /32Bit+
/// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool
/// </summary>
private static void EnsureRemoteExecutorIs32Bit()
{
string windowsSdksDir = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"),
"Microsoft SDKs", "Windows");
FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe");
string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe");
string args = $"{remoteExecutorPath} /32Bit+ /Force";
var si = new ProcessStartInfo()
{
FileName = corFlagsFile.FullName,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using var proc = Process.Start(si);
proc.WaitForExit();
string standardOutput = proc.StandardOutput.ReadToEnd();
string standardError = proc.StandardError.ReadToEnd();
if (proc.ExitCode != 0)
{
throw new Exception(
$@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}");
}
static FileInfo Find(DirectoryInfo root, string name)
{
FileInfo fi = root.EnumerateFiles(name).FirstOrDefault();
if (fi != null)
{
return fi;
}
foreach (DirectoryInfo dir in root.EnumerateDirectories())
{
fi = Find(dir, name);
if (fi != null)
{
return fi;
}
}
return null;
}
}
/// <summary>
/// Solution borrowed from:
/// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100
@ -146,4 +238,4 @@ namespace SixLabors.ImageSharp.Tests
return "";
}
}
}
}

5
tests/ImageSharp.Tests/TestUtilities/TestPixel.cs

@ -29,11 +29,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
public float Blue { get; set; }
public float Alpha { get; set; }
public static implicit operator TPixel(TestPixel<TPixel> d)
{
return d?.AsPixel() ?? default(TPixel);
}
public TPixel AsPixel()
{
TPixel pix = default(TPixel);

62
tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs

@ -0,0 +1,62 @@
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests
{
public class BasicSerializerTests
{
class BaseObj : IXunitSerializable
{
public double Length { get; set; }
public string Name { get; set; }
public int Lives { get; set; }
public virtual void Deserialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(Length), Length);
info.AddValue(nameof(Name), Name);
info.AddValue(nameof(this.Lives), Lives);
}
public virtual void Serialize(IXunitSerializationInfo info)
{
this.Length = info.GetValue<double>(nameof(Length));
this.Name = info.GetValue<string>(nameof(Name));
this.Lives = info.GetValue<int>(nameof(Lives));
}
}
class DerivedObj : BaseObj
{
public double Strength { get; set; }
public override void Deserialize(IXunitSerializationInfo info)
{
this.Strength = info.GetValue<double>(nameof(Strength));
base.Deserialize(info);
}
public override void Serialize(IXunitSerializationInfo info)
{
base.Serialize(info);
info.AddValue(nameof(Strength), Strength);
}
}
[Fact]
public void SerializeDeserialize_ShouldPreserveValues()
{
var obj = new DerivedObj() {Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8};
string str = BasicSerializer.Serialize(obj);
BaseObj mirrorBase = BasicSerializer.Deserialize<BaseObj>(str);
DerivedObj mirror = Assert.IsType<DerivedObj>(mirrorBase);
Assert.Equal(obj.Length, mirror.Length);
Assert.Equal(obj.Name, mirror.Name);
Assert.Equal(obj.Lives, mirror.Lives);
Assert.Equal(obj.Strength, mirror.Strength);
}
}
}
Loading…
Cancel
Save