// 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 Deserialize(string value) where T : IXunitSerializable => BasicSerializer.Deserialize(value); /// /// 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()); } } } 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 } }