From 613a7c19f564e9e33af57dd4dcff909f55cbefca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 18:45:53 +0000 Subject: [PATCH 1/4] Add CulturedConditionalFact/Theory attributes and tests Agent-Logs-Url: https://github.com/dotnet/arcade/sessions/d4c6180b-97a0-40ce-b44f-563c59ba63bd Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../src/CulturedConditionalFactAttribute.cs | 31 +++++ .../src/CulturedConditionalTheoryAttribute.cs | 31 +++++ .../CulturedConditionalAttributeTests.cs | 119 ++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs create mode 100644 src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs create mode 100644 src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs new file mode 100644 index 00000000000..962acc249d3 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs @@ -0,0 +1,31 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.XUnitExtensions; + +namespace Xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class CulturedConditionalFactAttribute : CulturedFactAttribute + { + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + public Type CalleeType { get; private set; } + public string[] ConditionMemberNames { get; private set; } + + public CulturedConditionalFactAttribute( + string[] cultures, + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + Type calleeType, + params string[] conditionMemberNames) + : base(cultures) + { + CalleeType = calleeType; + ConditionMemberNames = conditionMemberNames; + string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); + if (skipReason != null) + Skip = skipReason; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs new file mode 100644 index 00000000000..cf5dfca92d8 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs @@ -0,0 +1,31 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.XUnitExtensions; + +namespace Xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class CulturedConditionalTheoryAttribute : CulturedTheoryAttribute + { + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + public Type CalleeType { get; private set; } + public string[] ConditionMemberNames { get; private set; } + + public CulturedConditionalTheoryAttribute( + string[] cultures, + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + Type calleeType, + params string[] conditionMemberNames) + : base(cultures) + { + CalleeType = calleeType; + ConditionMemberNames = conditionMemberNames; + string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); + if (skipReason != null) + Skip = skipReason; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs new file mode 100644 index 00000000000..112fa70f78b --- /dev/null +++ b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using Xunit; + +namespace Microsoft.DotNet.XUnitExtensions.Tests +{ + public class CulturedConditionalAttributeTests + { + // These tests validate the xunit v3 cultured conditional attributes without relying on + // execution order, which the v3 runner does not guarantee for this scenario. + + private static readonly string[] s_cultures = new[] { "en-US", "fr-FR" }; + + public static bool AlwaysTrue => true; + public static bool AlwaysFalse => false; + + [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysTrue))] + public void CulturedConditionalFactTrue() + { + // The current culture must be one of the requested cultures when the test runs. + Assert.Contains(CultureInfo.CurrentCulture.Name, s_cultures); + } + + [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysFalse))] + public void CulturedConditionalFactFalse() + { + Assert.Fail("This test should have been skipped."); + } + + [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysTrue))] + [InlineData(1, "one")] + [InlineData(2, "two")] + public void CulturedConditionalTheoryTrue(int number, string name) + { + // Verify the arguments were actually passed through and the culture was set. + Assert.True(number > 0); + Assert.False(string.IsNullOrEmpty(name)); + Assert.Contains(CultureInfo.CurrentCulture.Name, s_cultures); + } + + [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysFalse))] + [InlineData(1)] + [InlineData(2)] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void CulturedConditionalTheoryFalse(int value) +#pragma warning restore xUnit1026 + { + Assert.Fail($"This test should have been skipped, but ran with value {value}."); + } + + [Fact] + public void ValidateCulturedConditionalFactSkipState() + { + Assert.Null(GetCulturedConditionalFactAttribute(nameof(CulturedConditionalFactTrue)).Skip); + Assert.Equal("Condition(s) not met: \"AlwaysFalse\"", GetCulturedConditionalFactAttribute(nameof(CulturedConditionalFactFalse)).Skip); + } + + [Fact] + public void ValidateCulturedConditionalTheorySkipState() + { + Assert.Null(GetCulturedConditionalTheoryAttribute(nameof(CulturedConditionalTheoryTrue)).Skip); + Assert.Equal("Condition(s) not met: \"AlwaysFalse\"", GetCulturedConditionalTheoryAttribute(nameof(CulturedConditionalTheoryFalse)).Skip); + } + + [Fact] + public void ValidateCulturedConditionalFactProperties() + { + CulturedConditionalFactAttribute attribute = GetCulturedConditionalFactAttribute(nameof(CulturedConditionalFactTrue)); + Assert.Equal(typeof(CulturedConditionalAttributeTests), attribute.CalleeType); + Assert.Equal(new[] { nameof(AlwaysTrue) }, attribute.ConditionMemberNames); + Assert.Equal(s_cultures, attribute.Cultures); + } + + [Fact] + public void ValidateCulturedConditionalTheoryProperties() + { + CulturedConditionalTheoryAttribute attribute = GetCulturedConditionalTheoryAttribute(nameof(CulturedConditionalTheoryTrue)); + Assert.Equal(typeof(CulturedConditionalAttributeTests), attribute.CalleeType); + Assert.Equal(new[] { nameof(AlwaysTrue) }, attribute.ConditionMemberNames); + Assert.Equal(s_cultures, attribute.Cultures); + } + + [Fact] + public void CulturedConditionalFactIsCulturedFact() + { + // Verify the inheritance relationship: a CulturedConditionalFact is a CulturedFact (and therefore a Fact). + CulturedConditionalFactAttribute attribute = GetCulturedConditionalFactAttribute(nameof(CulturedConditionalFactTrue)); + Assert.IsAssignableFrom(attribute); + Assert.IsAssignableFrom(attribute); + } + + [Fact] + public void CulturedConditionalTheoryIsCulturedTheory() + { + // Verify the inheritance relationship: a CulturedConditionalTheory is a CulturedTheory (and therefore a Theory). + CulturedConditionalTheoryAttribute attribute = GetCulturedConditionalTheoryAttribute(nameof(CulturedConditionalTheoryTrue)); + Assert.IsAssignableFrom(attribute); + Assert.IsAssignableFrom(attribute); + } + + private static CulturedConditionalFactAttribute GetCulturedConditionalFactAttribute(string methodName) + { + return (CulturedConditionalFactAttribute)typeof(CulturedConditionalAttributeTests) + .GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public)! + .GetCustomAttribute(typeof(CulturedConditionalFactAttribute), inherit: false)!; + } + + private static CulturedConditionalTheoryAttribute GetCulturedConditionalTheoryAttribute(string methodName) + { + return (CulturedConditionalTheoryAttribute)typeof(CulturedConditionalAttributeTests) + .GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public)! + .GetCustomAttribute(typeof(CulturedConditionalTheoryAttribute), inherit: false)!; + } + } +} From 61bf11c0632b9f6763200c1c4c3a80a14f990772 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 21:42:11 +0000 Subject: [PATCH 2/4] Add braces around if blocks and remove redundant inheritance tests Agent-Logs-Url: https://github.com/dotnet/arcade/sessions/0596fa58-4ded-403f-84c8-d8a179fb05c9 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../src/CulturedConditionalFactAttribute.cs | 2 ++ .../src/CulturedConditionalTheoryAttribute.cs | 2 ++ .../tests/CulturedConditionalAttributeTests.cs | 18 ------------------ 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs index 962acc249d3..1ecd9f917aa 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs @@ -25,7 +25,9 @@ public CulturedConditionalFactAttribute( ConditionMemberNames = conditionMemberNames; string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); if (skipReason != null) + { Skip = skipReason; + } } } } diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs index cf5dfca92d8..aae6cfdcc27 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs @@ -25,7 +25,9 @@ public CulturedConditionalTheoryAttribute( ConditionMemberNames = conditionMemberNames; string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); if (skipReason != null) + { Skip = skipReason; + } } } } diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs index 112fa70f78b..5a5c83968b9 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs @@ -84,24 +84,6 @@ public void ValidateCulturedConditionalTheoryProperties() Assert.Equal(s_cultures, attribute.Cultures); } - [Fact] - public void CulturedConditionalFactIsCulturedFact() - { - // Verify the inheritance relationship: a CulturedConditionalFact is a CulturedFact (and therefore a Fact). - CulturedConditionalFactAttribute attribute = GetCulturedConditionalFactAttribute(nameof(CulturedConditionalFactTrue)); - Assert.IsAssignableFrom(attribute); - Assert.IsAssignableFrom(attribute); - } - - [Fact] - public void CulturedConditionalTheoryIsCulturedTheory() - { - // Verify the inheritance relationship: a CulturedConditionalTheory is a CulturedTheory (and therefore a Theory). - CulturedConditionalTheoryAttribute attribute = GetCulturedConditionalTheoryAttribute(nameof(CulturedConditionalTheoryTrue)); - Assert.IsAssignableFrom(attribute); - Assert.IsAssignableFrom(attribute); - } - private static CulturedConditionalFactAttribute GetCulturedConditionalFactAttribute(string methodName) { return (CulturedConditionalFactAttribute)typeof(CulturedConditionalAttributeTests) From e7bd79a36f112ff9aaf50d05349d59ff0906076e Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Sun, 17 May 2026 14:58:45 -0700 Subject: [PATCH 3/4] Remove extra using Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../tests/CulturedConditionalAttributeTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs index 5a5c83968b9..779591f0ed2 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Globalization; using System.Reflection; using Xunit; From 729688c8eb208591b7f17f55a4bfbc083d7798ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 22:39:33 +0000 Subject: [PATCH 4/4] Add source information constructor parameters to avoid xUnit3003 errors Agent-Logs-Url: https://github.com/dotnet/arcade/sessions/b12008fd-862a-4d58-a6f5-20cd50be2182 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../src/CulturedConditionalFactAttribute.cs | 7 +++++-- .../src/CulturedConditionalTheoryAttribute.cs | 7 +++++-- .../tests/CulturedConditionalAttributeTests.cs | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs index 1ecd9f917aa..be3f7232333 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalFactAttribute.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.DotNet.XUnitExtensions; namespace Xunit @@ -18,8 +19,10 @@ public CulturedConditionalFactAttribute( string[] cultures, [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] Type calleeType, - params string[] conditionMemberNames) - : base(cultures) + string[] conditionMemberNames, + [CallerFilePath] string sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = 0) + : base(cultures, sourceFilePath, sourceLineNumber) { CalleeType = calleeType; ConditionMemberNames = conditionMemberNames; diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs index aae6cfdcc27..5f0d45d99a3 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/src/CulturedConditionalTheoryAttribute.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.DotNet.XUnitExtensions; namespace Xunit @@ -18,8 +19,10 @@ public CulturedConditionalTheoryAttribute( string[] cultures, [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] Type calleeType, - params string[] conditionMemberNames) - : base(cultures) + string[] conditionMemberNames, + [CallerFilePath] string sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = 0) + : base(cultures, sourceFilePath, sourceLineNumber) { CalleeType = calleeType; ConditionMemberNames = conditionMemberNames; diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs index 779591f0ed2..ceaa8069e89 100644 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs +++ b/src/Microsoft.DotNet.XUnitV3Extensions/tests/CulturedConditionalAttributeTests.cs @@ -17,20 +17,20 @@ public class CulturedConditionalAttributeTests public static bool AlwaysTrue => true; public static bool AlwaysFalse => false; - [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysTrue))] + [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), new[] { nameof(AlwaysTrue) })] public void CulturedConditionalFactTrue() { // The current culture must be one of the requested cultures when the test runs. Assert.Contains(CultureInfo.CurrentCulture.Name, s_cultures); } - [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysFalse))] + [CulturedConditionalFact(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), new[] { nameof(AlwaysFalse) })] public void CulturedConditionalFactFalse() { Assert.Fail("This test should have been skipped."); } - [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysTrue))] + [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), new[] { nameof(AlwaysTrue) })] [InlineData(1, "one")] [InlineData(2, "two")] public void CulturedConditionalTheoryTrue(int number, string name) @@ -41,7 +41,7 @@ public void CulturedConditionalTheoryTrue(int number, string name) Assert.Contains(CultureInfo.CurrentCulture.Name, s_cultures); } - [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), nameof(AlwaysFalse))] + [CulturedConditionalTheory(new[] { "en-US", "fr-FR" }, typeof(CulturedConditionalAttributeTests), new[] { nameof(AlwaysFalse) })] [InlineData(1)] [InlineData(2)] #pragma warning disable xUnit1026 // Theory methods should use all of their parameters