diff --git a/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchFrameworkDiscoverer.cs b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchFrameworkDiscoverer.cs new file mode 100644 index 0000000000..cb03fc02a2 --- /dev/null +++ b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchFrameworkDiscoverer.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel; +using System.Linq; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Silk.NET.SilkTouch.TestFramework; + +public sealed class SilkTouchFrameworkDiscoverer : XunitTestFrameworkDiscoverer +{ + public SilkTouchFrameworkDiscoverer( + IAssemblyInfo assemblyInfo, + ISourceInformationProvider sourceProvider, + IMessageSink diagnosticMessageSink, + IXunitTestCollectionFactory? collectionFactory = null) + : base( + assemblyInfo, + sourceProvider, + diagnosticMessageSink, + collectionFactory) + { + } + + protected override bool FindTestsForMethod + ( + ITestMethod testMethod, + bool includeSourceInformation, + IMessageBus messageBus, + ITestFrameworkDiscoveryOptions discoveryOptions + ) + { + var traits = testMethod.Method.GetCustomAttributes(typeof(TraitAttribute)) + .Select + ( + x => + { + var args = x.GetConstructorArguments().ToArray(); + var name = (string) args[0]; + var value = (string) args[1]; + return (name, value); + } + ); + foreach (var (name, value) in traits) + { + if (name is "Category" && + value is not "Integration" and not "Scraper" and not "Symbols" and not "Emitter" and not "TypeStore" and not "Type Resolution") + { + return this.ReportDiscoveredTestCase + ( + (ITestCase) new ExecutionErrorTestCase + ( + this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, + testMethod, + "Category " + value + + " is not a valid category. Allowed values are \"Integration\", \"Scraper\", \"Symbols\", \"Emitter\"." + ), includeSourceInformation, messageBus + ); + } + + if (name is "Source Language" && + value is not "C++") + { + return this.ReportDiscoveredTestCase + ( + (ITestCase) new ExecutionErrorTestCase + ( + this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, + testMethod, + "Source Language " + value + + " is not a valid language. Allowed values are \"C++\"." + ), includeSourceInformation, messageBus + ); + } + + if (name is "Target Language" && + value is not "C#") + { + return this.ReportDiscoveredTestCase + ( + (ITestCase) new ExecutionErrorTestCase + ( + this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, + testMethod, + "Target Language " + value + + " is not a valid language. Allowed values are \"C#\"." + ), includeSourceInformation, messageBus + ); + } + + if (name is "Feature") + { + if (!SilkTouchTestFramework.Features.TryGetValue(value, out var flag)) + { + return this.ReportDiscoveredTestCase + ( + (ITestCase) new ExecutionErrorTestCase + ( + this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, + TestMethodDisplayOptions.None, testMethod, + "Feature Flag " + value + " is not a valid flag. Allowed values are " + String.Join + (", ", SilkTouchTestFramework.Features.Keys.Select(x => "\"" + x + "\"")) + "." + ), includeSourceInformation, messageBus + ); + } + + if (!flag) + { + return this.ReportDiscoveredTestCase + ( + (ITestCase) new DisabledTestCase + ( + "Flag " + value + " is not enabled", this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, + TestMethodDisplayOptions.None, testMethod + ), includeSourceInformation, messageBus + ); + } + } + } + return base.FindTestsForMethod(testMethod, includeSourceInformation, messageBus, discoveryOptions); + } + + private sealed class DisabledTestCase : XunitTestCase + { + private readonly string _skipReason; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete + ( + "Called by the de-serializer; should only be called by deriving classes for de-serialization purposes" + )] + public DisabledTestCase() : base() + { + _skipReason = "Generic Skip"; + } + + public DisabledTestCase + ( + string skipReason, + IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + TestMethodDisplayOptions defaultMethodDisplayOptions, + ITestMethod testMethod, + object[]? testMethodArguments = null + ) : base + ( + diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, + testMethodArguments + ) + { + _skipReason = skipReason; + } + + protected override string GetSkipReason(IAttributeInfo factAttribute) + { + return _skipReason; + } + } +} diff --git a/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFramework.cs b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFramework.cs index 9c32e43bd2..d2b83d3d4c 100644 --- a/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFramework.cs +++ b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFramework.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; +using System.Collections.Generic; +using System.Reflection; using System.Xml; -using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -35,157 +32,8 @@ protected override ITestFrameworkDiscoverer CreateDiscoverer( assemblyInfo, SourceInformationProvider, DiagnosticMessageSink); - } - - public class SilkTouchFrameworkDiscoverer : XunitTestFrameworkDiscoverer - { - public SilkTouchFrameworkDiscoverer( - IAssemblyInfo assemblyInfo, - ISourceInformationProvider sourceProvider, - IMessageSink diagnosticMessageSink, - IXunitTestCollectionFactory? collectionFactory = null) - : base( - assemblyInfo, - sourceProvider, - diagnosticMessageSink, - collectionFactory) - { - } - - protected override bool FindTestsForMethod - ( - ITestMethod testMethod, - bool includeSourceInformation, - IMessageBus messageBus, - ITestFrameworkDiscoveryOptions discoveryOptions - ) - { - var traits = testMethod.Method.GetCustomAttributes(typeof(TraitAttribute)) - .Select - ( - x => - { - var args = x.GetConstructorArguments().ToArray(); - var name = (string) args[0]; - var value = (string) args[1]; - return (name, value); - } - ); - foreach (var (name, value) in traits) - { - if (name is "Category" && - value is not "Integration" and not "Scraper" and not "Symbols" and not "Emitter" and not "TypeStore" and not "Type Resolution") - { - return this.ReportDiscoveredTestCase - ( - (ITestCase) new ExecutionErrorTestCase - ( - this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, - testMethod, - "Category " + value + - " is not a valid category. Allowed values are \"Integration\", \"Scraper\", \"Symbols\", \"Emitter\"." - ), includeSourceInformation, messageBus - ); - } - - if (name is "Source Language" && - value is not "C++") - { - return this.ReportDiscoveredTestCase - ( - (ITestCase) new ExecutionErrorTestCase - ( - this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, - testMethod, - "Source Language " + value + - " is not a valid language. Allowed values are \"C++\"." - ), includeSourceInformation, messageBus - ); - } - - if (name is "Target Language" && - value is not "C#") - { - return this.ReportDiscoveredTestCase - ( - (ITestCase) new ExecutionErrorTestCase - ( - this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, - testMethod, - "Target Language " + value + - " is not a valid language. Allowed values are \"C#\"." - ), includeSourceInformation, messageBus - ); - } - if (name is "Feature") - { - if (!SilkTouchTestFramework.Features.TryGetValue(value, out var flag)) - { - return this.ReportDiscoveredTestCase - ( - (ITestCase) new ExecutionErrorTestCase - ( - this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, - TestMethodDisplayOptions.None, testMethod, - "Feature Flag " + value + " is not a valid flag. Allowed values are " + String.Join - (", ", SilkTouchTestFramework.Features.Keys.Select(x => "\"" + x + "\"")) + "." - ), includeSourceInformation, messageBus - ); - } - - if (!flag) - { - return this.ReportDiscoveredTestCase - ( - (ITestCase) new DisabledTestCase - ( - "Flag " + value + " is not enabled", this.DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, - TestMethodDisplayOptions.None, testMethod - ), includeSourceInformation, messageBus - ); - } - } - } - return base.FindTestsForMethod(testMethod, includeSourceInformation, messageBus, discoveryOptions); - } - - private sealed class DisabledTestCase : XunitTestCase - { - private readonly string _skipReason; - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete - ( - "Called by the de-serializer; should only be called by deriving classes for de-serialization purposes" - )] - public DisabledTestCase() : base() - { - _skipReason = "Generic Skip"; - } - - public DisabledTestCase - ( - string skipReason, - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, - object[]? testMethodArguments = null - ) : base - ( - diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, - testMethodArguments - ) - { - _skipReason = skipReason; - } - - protected override string GetSkipReason(IAttributeInfo factAttribute) - { - return _skipReason; - } - } + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new SilkTouchTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); } } diff --git a/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFrameworkExecutor.cs b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFrameworkExecutor.cs new file mode 100644 index 0000000000..e37b522912 --- /dev/null +++ b/tests/Silk.NET.SilkTouch.TestFramework/SilkTouchTestFrameworkExecutor.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ATTRIBUTION: +// THIS FILE IS A MODIFIED VERSION OF CODE FOUND AT +// https://www.meziantou.net/parallelize-test-cases-execution-in-xunit.htm +// AND https://github.com/meziantou/Meziantou.Xunit.ParallelTestFramework +// WITH A NUGET PACKAGE AT https://www.nuget.org/packages/Meziantou.Xunit.ParallelTestFramework/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Silk.NET.SilkTouch.TestFramework; + +public sealed class SilkTouchTestFrameworkExecutor : XunitTestFrameworkExecutor +{ + + public SilkTouchTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) + { + } + + protected override void RunTestCases + (IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) + { + var newTestCases = AllInParallel(testCases); + + base.RunTestCases(newTestCases, executionMessageSink, executionOptions); + } + + private IEnumerable AllInParallel(IEnumerable testCases) + { + return testCases.Select + ( + x => + { + var oldTestMethod = x.TestMethod; + var oldTestClass = oldTestMethod.TestClass; + var oldTestCollection = oldTestMethod.TestClass.TestCollection; + + if (oldTestCollection.CollectionDefinition != null || oldTestClass.Class.GetCustomAttributes + (typeof(CollectionAttribute)) + .Any()) + return x; + + return AssignToNewCollection(x); + } + ); + } + + private IXunitTestCase AssignToNewCollection(IXunitTestCase testCase) + { + static TestMethodDisplay GetTestMethodDisplay(TestMethodTestCase testCase) + { + return (TestMethodDisplay)typeof(TestMethodTestCase) + .GetProperty("DefaultMethodDisplay", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(testCase)!; + } + + static TestMethodDisplayOptions GetTestMethodDisplayOptions(TestMethodTestCase testCase) + { + return (TestMethodDisplayOptions)typeof(TestMethodTestCase) + .GetProperty("DefaultMethodDisplayOptions", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(testCase)!; + } + + var oldTestMethod = testCase.TestMethod; + var oldTestClass = oldTestMethod.TestClass; + var oldTestCollection = oldTestMethod.TestClass.TestCollection; + + // Create a new collection with a unique id for the test case. + var newTestCollection = new TestCollection + ( + oldTestCollection.TestAssembly, + oldTestCollection.CollectionDefinition, + displayName: $"{oldTestCollection.DisplayName} {oldTestCollection.UniqueID}" + ); + newTestCollection.UniqueID = Guid.NewGuid(); + + // Duplicate the test and assign it to the new collection + var newTestClass = new TestClass(newTestCollection, oldTestClass.Class); + var newTestMethod = new TestMethod(newTestClass, oldTestMethod.Method); + switch (testCase) + { + // Used by Theory having DisableDiscoveryEnumeration or non-serializable data + case XunitTheoryTestCase xunitTheoryTestCase: + return new XunitTheoryTestCase + ( + DiagnosticMessageSink, + GetTestMethodDisplay(xunitTheoryTestCase), + GetTestMethodDisplayOptions(xunitTheoryTestCase), + newTestMethod + ); + + // Used by all other tests + case XunitTestCase xunitTestCase: + return new XunitTestCase + ( + DiagnosticMessageSink, + GetTestMethodDisplay(xunitTestCase), + GetTestMethodDisplayOptions(xunitTestCase), + newTestMethod, + xunitTestCase.TestMethodArguments + ); + + // TODO If you use custom attribute, you may need to add cases here + + default: + throw new ArgumentOutOfRangeException("Test case " + testCase.GetType() + " not supported"); + } + } +} diff --git a/tests/Silk.NET.SilkTouch.Tests.Common/Fakers.cs b/tests/Silk.NET.SilkTouch.Tests.Common/Fakers.cs index 927674fa4a..7bf42bd3d0 100644 --- a/tests/Silk.NET.SilkTouch.Tests.Common/Fakers.cs +++ b/tests/Silk.NET.SilkTouch.Tests.Common/Fakers.cs @@ -14,7 +14,7 @@ static Fakers() Faker.DefaultStrictMode = true; } - public static int StandardGenerateCount { get; } = 10; + public static int StandardGenerateCount { get; } = 20; public static Faker IdentifierSymbol { get; } = new Faker().SkipConstructor() .RuleFor