Browse Source

Add FeatureTestRunner

js/color-alpha-handling
James Jackson-South 6 years ago
parent
commit
310710e311
  1. 266
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
  2. 237
      tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

266
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -0,0 +1,266 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.DotNet.RemoteExecutor;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.TestUtilities
{
/// <summary>
/// Allows the testing against specific feature sets.
/// </summary>
public static class FeatureTestRunner
{
private static readonly char[] SplitChars = new[] { ',', ' ' };
/// <summary>
/// Allows the deserialization of parameters passed to the feature test.
/// <remark>
/// <para>
/// This is required because <see cref="RemoteExecutor"/> does not allow
/// marshalling of fields so we cannot pass a wrapped <see cref="Action{T}"/>
/// allowing automatic deserialization.
/// </para>
/// </remark>
/// </summary>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <param name="value">The string value to deserialize.</param>
/// <returns>The <see cref="T"/> value.</returns>
public static T Deserialize<T>(string value)
where T : IXunitSerializable
=> BasicSerializer.Deserialize<T>(value);
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">The test action to run.</param>
/// <param name="intrinsics">The intrinsics features.</param>
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics)
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0";
RemoteExecutor.Invoke(
action,
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action();
}
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">
/// The test action to run.
/// The parameter passed will be a string representing the currently testing <see cref="HwIntrinsics"/>.</param>
/// <param name="intrinsics">The intrinsics features.</param>
public static void RunWithHwIntrinsicsFeature(
Action<string> action,
HwIntrinsics intrinsics)
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0";
RemoteExecutor.Invoke(
action,
intrinsic.Key.ToString(),
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action(intrinsic.Key.ToString());
}
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">The test action to run.</param>
/// <param name="intrinsics">The intrinsics features.</param>
/// <param name="serializable">The value to pass as a parameter to the test action.</param>
public static void RunWithHwIntrinsicsFeature<T>(
Action<string> action,
HwIntrinsics intrinsics,
T serializable)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0";
RemoteExecutor.Invoke(
action,
BasicSerializer.Serialize(serializable),
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action(BasicSerializer.Serialize(serializable));
}
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">The test action to run.</param>
/// <param name="intrinsics">The intrinsics features.</param>
/// <param name="serializable">The value to pass as a parameter to the test action.</param>
public static void RunWithHwIntrinsicsFeature<T>(
Action<string, string> action,
HwIntrinsics intrinsics,
T serializable)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_Enable{intrinsic}"] = "0";
RemoteExecutor.Invoke(
action,
BasicSerializer.Serialize(serializable),
intrinsic.Key.ToString(),
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action(BasicSerializer.Serialize(serializable), intrinsic.Key.ToString());
}
}
}
internal static Dictionary<HwIntrinsics, string> ToFeatureKeyValueCollection(this HwIntrinsics intrinsics)
{
// Loop through and translate the given values into COMPlus equivaluents
var features = new Dictionary<HwIntrinsics, string>();
foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries))
{
var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic);
switch (intrinsic)
{
case nameof(HwIntrinsics.DisableSIMD):
features.Add(key, "FeatureSIMD");
break;
case nameof(HwIntrinsics.AllowAll):
// Not a COMPlus value. We filter in calling method.
features.Add(key, nameof(HwIntrinsics.AllowAll));
break;
default:
features.Add(key, intrinsic.Replace("Disable", "Enable"));
break;
}
}
return features;
}
}
/// <summary>
/// See <see href="https://github.com/dotnet/runtime/blob/50ac454d8d8a1915188b2a4bb3fff3b81bf6c0cf/src/coreclr/src/jit/jitconfigvalues.h#L224"/>
/// <remarks>
/// <see cref="DisableSIMD"/> ends up impacting all SIMD support(including System.Numerics)
/// but not things like <see cref="DisableBMI1"/>, <see cref="DisableBMI2"/>, and <see cref="DisableLZCNT"/>.
/// </remarks>
/// </summary>
[Flags]
#pragma warning disable RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute).
public enum HwIntrinsics
#pragma warning restore RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute).
{
// Use flags so we can pass multiple values without using params.
// Don't base on 0 or use inverse for All as that doesn't translate to string values.
DisableSIMD = 1 << 0,
DisableHWIntrinsic = 1 << 1,
DisableSSE = 1 << 2,
DisableSSE2 = 1 << 3,
DisableAES = 1 << 4,
DisablePCLMULQDQ = 1 << 5,
DisableSSE3 = 1 << 6,
DisableSSSE3 = 1 << 7,
DisableSSE41 = 1 << 8,
DisableSSE42 = 1 << 9,
DisablePOPCNT = 1 << 10,
DisableAVX = 1 << 11,
DisableFMA = 1 << 12,
DisableAVX2 = 1 << 13,
DisableBMI1 = 1 << 14,
DisableBMI2 = 1 << 15,
DisableLZCNT = 1 << 16,
AllowAll = 1 << 17
}
}

237
tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

@ -0,0 +1,237 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics.X86;
#endif
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
{
public class FeatureTestRunnerTests
{
public static TheoryData<HwIntrinsics, string[]> Intrinsics =>
new TheoryData<HwIntrinsics, string[]>
{
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } },
{ HwIntrinsics.DisableSIMD | HwIntrinsics.DisableHWIntrinsic, new string[] { "FeatureSIMD", "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } }
};
[Theory]
[MemberData(nameof(Intrinsics))]
public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedItrinsics, string[] expectedValues)
{
Dictionary<HwIntrinsics, string> features = expectedItrinsics.ToFeatureKeyValueCollection();
HwIntrinsics[] keys = features.Keys.ToArray();
HwIntrinsics actualIntrinsics = keys[0];
for (int i = 1; i < keys.Length; i++)
{
actualIntrinsics |= keys[i];
}
Assert.Equal(expectedItrinsics, actualIntrinsics);
IEnumerable<string> actualValues = features.Select(x => x.Value);
Assert.Equal(expectedValues, actualValues);
}
[Fact]
public void AllowsAllHwIntrinsicFeatures()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
() => Assert.True(Vector.IsHardwareAccelerated),
HwIntrinsics.AllowAll);
}
[Fact]
public void CanLimitHwIntrinsicFeatures()
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
() => Assert.False(Vector.IsHardwareAccelerated),
HwIntrinsics.DisableSIMD);
}
[Fact]
public void CanLimitHwIntrinsicFeaturesWithIntrinsicsParam()
{
static void AssertHwIntrinsicsFeatureDisabled(string intrinsic)
{
Assert.NotNull(intrinsic);
switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
{
case HwIntrinsics.DisableSIMD:
Assert.False(Vector.IsHardwareAccelerated);
break;
#if SUPPORTS_RUNTIME_INTRINSICS
case HwIntrinsics.DisableHWIntrinsic:
Assert.False(Vector.IsHardwareAccelerated);
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
break;
case HwIntrinsics.DisableSSE2:
Assert.False(Sse2.IsSupported);
break;
case HwIntrinsics.DisableAES:
Assert.False(Aes.IsSupported);
break;
case HwIntrinsics.DisablePCLMULQDQ:
Assert.False(Pclmulqdq.IsSupported);
break;
case HwIntrinsics.DisableSSE3:
Assert.False(Sse3.IsSupported);
break;
case HwIntrinsics.DisableSSSE3:
Assert.False(Ssse3.IsSupported);
break;
case HwIntrinsics.DisableSSE41:
Assert.False(Sse41.IsSupported);
break;
case HwIntrinsics.DisableSSE42:
Assert.False(Sse42.IsSupported);
break;
case HwIntrinsics.DisablePOPCNT:
Assert.False(Popcnt.IsSupported);
break;
case HwIntrinsics.DisableAVX:
Assert.False(Avx.IsSupported);
break;
case HwIntrinsics.DisableFMA:
Assert.False(Fma.IsSupported);
break;
case HwIntrinsics.DisableAVX2:
Assert.False(Avx2.IsSupported);
break;
case HwIntrinsics.DisableBMI1:
Assert.False(Bmi1.IsSupported);
break;
case HwIntrinsics.DisableBMI2:
Assert.False(Bmi2.IsSupported);
break;
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
#endif
}
}
foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics)))
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic);
}
}
[Fact]
public void CanLimitHwIntrinsicFeaturesWithSerializableParam()
{
static void AssertHwIntrinsicsFeatureDisabled(string serializable)
{
Assert.NotNull(serializable);
Assert.NotNull(FeatureTestRunner.Deserialize<FakeSerializable>(serializable));
#if SUPPORTS_RUNTIME_INTRINSICS
Assert.False(Sse.IsSupported);
#endif
}
foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics)))
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
AssertHwIntrinsicsFeatureDisabled,
HwIntrinsics.DisableSSE,
new FakeSerializable());
}
}
[Fact]
public void CanLimitHwIntrinsicFeaturesWithSerializableAndIntrinsicsParams()
{
static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic)
{
Assert.NotNull(serializable);
Assert.NotNull(FeatureTestRunner.Deserialize<FakeSerializable>(serializable));
switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
{
case HwIntrinsics.DisableSIMD:
Assert.False(Vector.IsHardwareAccelerated);
break;
#if SUPPORTS_RUNTIME_INTRINSICS
case HwIntrinsics.DisableHWIntrinsic:
Assert.False(Vector.IsHardwareAccelerated);
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
break;
case HwIntrinsics.DisableSSE2:
Assert.False(Sse2.IsSupported);
break;
case HwIntrinsics.DisableAES:
Assert.False(Aes.IsSupported);
break;
case HwIntrinsics.DisablePCLMULQDQ:
Assert.False(Pclmulqdq.IsSupported);
break;
case HwIntrinsics.DisableSSE3:
Assert.False(Sse3.IsSupported);
break;
case HwIntrinsics.DisableSSSE3:
Assert.False(Ssse3.IsSupported);
break;
case HwIntrinsics.DisableSSE41:
Assert.False(Sse41.IsSupported);
break;
case HwIntrinsics.DisableSSE42:
Assert.False(Sse42.IsSupported);
break;
case HwIntrinsics.DisablePOPCNT:
Assert.False(Popcnt.IsSupported);
break;
case HwIntrinsics.DisableAVX:
Assert.False(Avx.IsSupported);
break;
case HwIntrinsics.DisableFMA:
Assert.False(Fma.IsSupported);
break;
case HwIntrinsics.DisableAVX2:
Assert.False(Avx2.IsSupported);
break;
case HwIntrinsics.DisableBMI1:
Assert.False(Bmi1.IsSupported);
break;
case HwIntrinsics.DisableBMI2:
Assert.False(Bmi2.IsSupported);
break;
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
#endif
}
}
foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics)))
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable());
}
}
public class FakeSerializable : IXunitSerializable
{
public void Deserialize(IXunitSerializationInfo info)
{
}
public void Serialize(IXunitSerializationInfo info)
{
}
}
}
}
Loading…
Cancel
Save