Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Xunit.StaFact/Sdk.Mac/CocoaFactDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions
return new ExecutionErrorTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported.");
}

return new UITestCase(UITestCase.SyncContextType.Cocoa, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, factAttribute);
return new UITestCase(UITestCase.SyncContextType.Cocoa, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, testMethodArguments: null, settings);
}
}
5 changes: 3 additions & 2 deletions src/Xunit.StaFact/Sdk.Mac/CocoaTheoryDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ public CocoaTheoryDiscoverer(IMessageSink diagnosticMessageSink)

protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
{
yield return new UITestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow);
UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, theoryAttribute);
yield return new UITestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow, settings);
}

protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
{
yield return new UITheoryTestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod);
yield return new UITheoryTestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, theoryAttribute);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions
return new ExecutionErrorTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported.");
}

UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, factAttribute);
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WinForms, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WinForms, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, testMethodArguments: null, settings)
: new XunitSkippedDataRowTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WinForms only exists on Windows.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ public WinFormsTheoryDiscoverer(IMessageSink diagnosticMessageSink)

protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
{
UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, theoryAttribute);
yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WinForms, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WinForms, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow, settings)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WinForms only exists on Windows.");
}

protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
{
yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.WinForms, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.WinForms, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, theoryAttribute)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WinForms only exists on Windows.");
}
}
3 changes: 2 additions & 1 deletion src/Xunit.StaFact/Sdk.WindowsDesktop/WpfFactDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions
return new ExecutionErrorTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported.");
}

UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, factAttribute);
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WPF, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WPF, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, testMethodArguments: null, settings)
: new XunitSkippedDataRowTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WPF only exists on Windows.");
}
}
5 changes: 3 additions & 2 deletions src/Xunit.StaFact/Sdk.WindowsDesktop/WpfTheoryDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ public WpfTheoryDiscoverer(IMessageSink diagnosticMessageSink)

protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
{
UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, theoryAttribute);
yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WPF, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.WPF, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow, settings)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WPF only exists on Windows.");
}

protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
{
yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.WPF, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.WPF, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, theoryAttribute)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "WPF only exists on Windows.");
}
}
3 changes: 2 additions & 1 deletion src/Xunit.StaFact/Sdk/StaFactDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions
return new ExecutionErrorTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported.");
}

UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, factAttribute);
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.None, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.None, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, testMethodArguments: null, settings)
: new XunitSkippedDataRowTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "STA threads only exist on Windows.");
}
}
5 changes: 3 additions & 2 deletions src/Xunit.StaFact/Sdk/StaTheoryDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFr
yield return new ExecutionErrorTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported.");
}

UISettingsKey settings = UIFactDiscoverer.GetSettings(testMethod, theoryAttribute);
yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.None, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow)
? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.None, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow, settings)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "STA threads only exist on Windows.");
}

Expand All @@ -51,7 +52,7 @@ protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFra
}

yield return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.None, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod)
? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.None, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, theoryAttribute)
: new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "STA threads only exist on Windows.");
}
}
99 changes: 98 additions & 1 deletion src/Xunit.StaFact/Sdk/UIFactDiscoverer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the Ms-PL license. See LICENSE file in the project root for full license information.

using System.Runtime.CompilerServices;

namespace Xunit.Sdk;

/// <summary>
Expand All @@ -20,8 +22,103 @@ public UIFactDiscoverer(IMessageSink diagnosticMessageSink)
this.diagnosticMessageSink = diagnosticMessageSink;
}

internal static UISettingsKey GetSettings(ITestMethod testMethod, IAttributeInfo factAttribute)
{
var maxAttempts = GetMaxAttempts(testMethod, factAttribute);
return new UISettingsKey(maxAttempts);
}

protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
{
return new UITestCase(UITestCase.SyncContextType.Portable, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
var maxAttempts = GetMaxAttempts(testMethod, factAttribute);
var settings = new UISettingsKey(maxAttempts);
return new UITestCase(UITestCase.SyncContextType.Portable, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, testMethodArguments: null, settings);
}

private static int GetMaxAttempts(ITestMethod testMethod, IAttributeInfo factAttribute)
{
return GetMaxAttempts(factAttribute, GetSettingsAttributes(testMethod).ToArray());
}

private static IEnumerable<IAttributeInfo> GetSettingsAttributes(ITestMethod testMethod)
{
foreach (IAttributeInfo attributeData in testMethod.Method.GetCustomAttributes(typeof(UISettingsAttribute)))
{
yield return attributeData;
}

foreach (IAttributeInfo attributeData in testMethod.TestClass.Class.GetCustomAttributes(typeof(UISettingsAttribute)))
{
yield return attributeData;
}
}

private static int GetMaxAttempts(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes)
{
return GetNamedArgument(
factAttribute,
settingsAttributes,
nameof(UISettingsAttribute.MaxAttempts),
static value => value > 0,
defaultValue: 1);
}

private static TValue GetNamedArgument<TValue>(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes, string argumentName, Func<TValue, bool> isValidValue, TValue defaultValue)
{
return GetNamedArgument(
factAttribute,
settingsAttributes,
argumentName,
isValidValue,
merge: null,
defaultValue);
}

private static TValue GetNamedArgument<TValue>(IAttributeInfo factAttribute, IAttributeInfo[] settingsAttributes, string argumentName, Func<TValue, bool> isValidValue, Func<TValue, TValue, TValue>? merge, TValue defaultValue)
{
StrongBox<TValue>? result = null;
if (TryGetNamedArgument(factAttribute, argumentName, isValidValue, out TValue? value))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 This was originally designed around having the properties be settable on both the fact attribute (e.g. WinFormsFactAttribute.MaxAttempts) and a separate settings attribute (in this case, UISettingsAttribute). I retained the full logic even though the current implementation only allows the values to be set in one location.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've dramatically simplified this class to focus on the feature you're adding.

{
if (merge is null)
{
return value;
}

result = new StrongBox<TValue>(value);
}

foreach (IAttributeInfo attribute in settingsAttributes)
{
if (TryGetNamedArgument(attribute, argumentName, isValidValue, out value))
{
if (merge is null)
{
return value;
}
else if (result is null)
{
result = new StrongBox<TValue>(value);
}
else
{
result.Value = merge(value, result.Value!);
}

return value;
}
}

if (result is not null)
{
return result.Value!;
}

return defaultValue;

static bool TryGetNamedArgument(IAttributeInfo attribute, string argumentName, Func<TValue, bool> isValidValue, out TValue value)
{
value = attribute.GetNamedArgument<TValue>(argumentName);
return isValidValue(value);
}
}
}
31 changes: 28 additions & 3 deletions src/Xunit.StaFact/Sdk/UITestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Xunit.Sdk;
[DebuggerDisplay(@"\{ class = {TestMethod.TestClass.Class.Name}, method = {TestMethod.Method.Name}, display = {DisplayName}, skip = {SkipReason} \}")]
public class UITestCase : XunitTestCase
{
private UISettingsKey settings;
private SyncContextType synchronizationContextType;

/// <summary>
Expand All @@ -22,15 +23,15 @@ public class UITestCase : XunitTestCase
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
[Obsolete("Call the constructor which provides UISettingsKey.")]
Comment thread
AArnott marked this conversation as resolved.
Outdated
public UITestCase(
SyncContextType synchronizationContextType,
IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
ITestMethod testMethod,
object?[]? testMethodArguments = null)
: base(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, testMethodArguments)
: this(synchronizationContextType, diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments, UISettingsKey.Default)
{
this.synchronizationContextType = synchronizationContextType;
}

/// <summary>
Expand All @@ -43,6 +44,28 @@ public UITestCase()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="UITestCase"/> class.
/// </summary>
/// <param name="synchronizationContextType">The type of <see cref="SynchronizationContext"/> to use.</param>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages.</param>
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="settings">The test settings to apply.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
internal UITestCase(
SyncContextType synchronizationContextType,
IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
ITestMethod testMethod,
object?[]? testMethodArguments,
UISettingsKey settings)
: base(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, testMethodArguments)
{
this.settings = settings;
this.synchronizationContextType = synchronizationContextType;
}

public enum SyncContextType
{
/// <summary>
Expand Down Expand Up @@ -85,6 +108,7 @@ public override void Serialize(IXunitSerializationInfo data)
}

base.Serialize(data);
data.AddValue(nameof(UISettingsAttribute.MaxAttempts), this.settings.MaxAttempts);
data.AddValue(nameof(this.synchronizationContextType), this.synchronizationContextType);
}

Expand All @@ -96,6 +120,7 @@ public override void Deserialize(IXunitSerializationInfo data)
}

base.Deserialize(data);
this.settings = new UISettingsKey(data.GetValue<int>(nameof(UISettingsAttribute.MaxAttempts)));
this.synchronizationContextType = (SyncContextType)data.GetValue(nameof(this.synchronizationContextType), typeof(SyncContextType));
}

Expand All @@ -117,7 +142,7 @@ public override Task<RunSummary> RunAsync(
{
using ThreadRental threadRental = await ThreadRental.CreateAsync(this.Adapter, this.TestMethod);
await threadRental.SynchronizationContext;
var runner = new UITestCaseRunner(this, this.DisplayName, this.SkipReason, constructorArguments, this.TestMethodArguments, messageBus, aggregator, cancellationTokenSource, threadRental);
var runner = new UITestCaseRunner(this, this.DisplayName, this.SkipReason, constructorArguments, this.TestMethodArguments, messageBus, aggregator, cancellationTokenSource, this.settings, threadRental);
return await runner.RunAsync();
},
cancellationTokenSource.Token);
Expand Down
Loading