// 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
{
///
/// Allows the testing against specific feature sets.
///
public static class FeatureTestRunner
{
private static readonly char[] SplitChars = new[] { ',', ' ' };
///
/// Allows the deserialization of parameters passed to the feature test.
///
///
/// This is required because does not allow
/// marshalling of fields so we cannot pass a wrapped
/// allowing automatic deserialization.
///
///
///
/// The type to deserialize to.
/// The string value to deserialize.
/// The value.
public static T DeserializeForXunit(string value)
where T : IXunitSerializable
=> BasicSerializer.Deserialize(value);
///
/// Allows the deserialization of types implementing
/// passed to the feature test.
///
/// The string value to deserialize.
/// The value.
public static T Deserialize(string value)
where T : IConvertible
=> (T)Convert.ChangeType(value, typeof(T));
///
/// Runs the given test within an environment
/// where the given features.
///
/// The test action to run.
/// The intrinsics features.
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics)
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "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();
}
}
}
///
/// Runs the given test within an environment
/// where the given features.
///
///
/// The test action to run.
/// The parameter passed will be a string representing the currently testing .
/// The intrinsics features.
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics)
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "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());
}
}
}
///
/// Runs the given test within an environment
/// where the given features.
///
/// The test action to run.
/// The intrinsics features.
/// The value to pass as a parameter to the test action.
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics,
T serializable)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "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));
}
}
}
///
/// Runs the given test within an environment
/// where the given features.
///
/// The test action to run.
/// The intrinsics features.
/// The value to pass as a parameter to the test action.
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics,
T serializable)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "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());
}
}
}
///
/// Runs the given test within an environment
/// where the given features.
///
/// The test action to run.
/// The intrinsics features.
/// The value to pass as a parameter to the test action.
/// The second value to pass as a parameter to the test action.
public static void RunWithHwIntrinsicsFeature(
Action action,
HwIntrinsics intrinsics,
T arg1,
T2 arg2)
where T : IXunitSerializable
where T2 : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
BasicSerializer.Serialize(arg1),
BasicSerializer.Serialize(arg2),
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(arg1), BasicSerializer.Serialize(arg2));
}
}
}
///
/// Runs the given test within an environment
/// where the given features.
///
/// The test action to run.
/// The value to pass as a parameter to the test action.
/// The intrinsics features.
public static void RunWithHwIntrinsicsFeature(
Action action,
T serializable,
HwIntrinsics intrinsics)
where T : IConvertible
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
serializable.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(serializable.ToString());
}
}
}
internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics)
{
// Loop through and translate the given values into COMPlus equivaluents
var features = new Dictionary();
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;
}
}
///
/// See
///
/// ends up impacting all SIMD support(including System.Numerics)
/// but not things like , , and .
///
///
[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
}
}