// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Diagnostics; using System.Globalization; 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 = [',', ' ']; /// /// 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 type of object to deserialize. /// The string value to deserialize. /// The value. public static T Deserialize(string value) where T : IConvertible => (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); /// /// 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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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 type of argument. /// 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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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 type of argument. /// 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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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 type of argument. /// The addition type of argument. /// 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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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 type of argument. /// 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, string arg2) where T : IXunitSerializable { if (!RemoteExecutor.IsSupported) { return; } foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, BasicSerializer.Serialize(arg1), 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), arg2); } } } /// /// Runs the given test within an environment /// where the given features. /// /// The type of argument. /// 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()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{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()); } } } /// /// Runs the given test within an environment /// where the given features. /// /// The type of argument. /// The test action to run. /// The value to pass as a parameter #0 to the test action. /// The value to pass as a parameter #1 to the test action. /// The intrinsics features. public static void RunWithHwIntrinsicsFeature( Action action, T arg0, T arg1, HwIntrinsics intrinsics) where T : IConvertible { if (!RemoteExecutor.IsSupported) { return; } foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) { ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, arg0.ToString(), arg1.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(arg0.ToString(), arg1.ToString()); } } } internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics) { // Loop through and translate the given values into COMPlus equivalents Dictionary features = []; foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) { HwIntrinsics key = Enum.Parse(intrinsic); switch (intrinsic) { 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 /// [Flags] #pragma warning disable RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute). public enum HwIntrinsics : long #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. DisableHWIntrinsic = 1L << 0, DisableSSE42 = 1L << 1, DisableAVX = 1L << 2, DisableAVX2 = 1L << 3, DisableAVX512 = 1L << 4, DisableAVX512v2 = 1L << 5, DisableAVX512v3 = 1L << 6, DisableAVX10v1 = 1L << 7, DisableAVX10v2 = 1L << 8, DisableAPX = 1L << 9, DisableAES = 1L << 10, DisableAVX512VP2INTERSECT = 1L << 11, DisableAVXIFMA = 1L << 12, DisableAVXVNNI = 1L << 13, DisableAVXVNNIINT = 1L << 14, DisableGFNI = 1L << 15, DisableSHA = 1L << 16, DisableVAES = 1L << 17, DisableWAITPKG = 1L << 18, DisableX86Serialize = 1 << 19, // Arm64 DisableArm64Aes = 1L << 20, DisableArm64Atomics = 1L << 21, DisableArm64Crc32 = 1L << 22, DisableArm64Dczva = 1L << 23, DisableArm64Dp = 1L << 24, DisableArm64Rdm = 1L << 25, DisableArm64Sha1 = 1L << 26, DisableArm64Sha256 = 1L << 27, DisableArm64Sve = 1L << 28, DisableArm64Sve2 = 1L << 29, // RISC-V64 DisableRiscV64Zba = 1L << 30, DisableRiscV64Zbb = 1L << 31, AllowAll = 1L << 32, }