diff --git a/.github/labeler.yml b/.github/labeler.yml index 92c6fef5..b8224c19 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -51,3 +51,10 @@ - any-glob-to-any-file: - "src/Allure.TestingPlatform/**" - "tests/Allure.TestingPlatform.Tests/**" + +"theme:xunit.v3": + - changed-files: + - any-glob-to-any-file: + - "src/Allure.Xunit.v3/**" + - "src/Allure.Xunit.v3.Generator/**" + - "tests/Allure.Xunit.v3.Tests/**" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c492b6a1..0faf1525 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: Test env: SOLUTION_PATH: allure-csharp.slnx BUILD_CONFIGURATION: 'Release' + PACK_CONFIGURATION: 'Publish' on: workflow_dispatch: @@ -53,7 +54,7 @@ jobs: dotnet pack ${{ env.SOLUTION_PATH }}\ --no-restore\ --no-build\ - --configuration ${{ env.BUILD_CONFIGURATION }} + --configuration ${{ env.PACK_CONFIGURATION }} - name: Build test samples run: | @@ -62,6 +63,7 @@ jobs: -p:Allure_SampleConfiguration=${{ env.BUILD_CONFIGURATION }} - name: 'Run test samples' + continue-on-error: true run: | dotnet msbuild ${{ env.SOLUTION_PATH }}\ -t:Allure_RunTestSamples\ @@ -85,6 +87,10 @@ jobs: --no-restore\ --no-build\ --configuration ${{ env.BUILD_CONFIGURATION }} + npx -y allure@3 run --config ./.allurerc.mjs --rerun 2 --environment="${{ env.BUILD_CONFIGURATION }}" --dump="allure-results-xunit-v3" -- dotnet run --project ./tests/Allure.Xunit.v3.Tests\ + --no-restore\ + --no-build\ + --configuration ${{ env.BUILD_CONFIGURATION }} npx -y allure@3 run --config ./.allurerc.mjs --rerun 2 --environment="${{ env.BUILD_CONFIGURATION }}" --dump="allure-results-specflow" -- dotnet test ./tests/Allure.SpecFlow.Tests\ --no-restore\ --no-build\ diff --git a/allure-csharp.slnx b/allure-csharp.slnx index e6bda7d1..6e96f624 100644 --- a/allure-csharp.slnx +++ b/allure-csharp.slnx @@ -67,6 +67,12 @@ + + + + + + @@ -96,6 +102,9 @@ + + + diff --git a/build/Allure.Build.Tasks/FillSampleMetadata.cs b/build/Allure.Build.Tasks/FillSampleMetadata.cs index dc42ffd1..02818ffd 100644 --- a/build/Allure.Build.Tasks/FillSampleMetadata.cs +++ b/build/Allure.Build.Tasks/FillSampleMetadata.cs @@ -20,6 +20,9 @@ public class FillSampleMetadata : Task [Required] public string ProjectDirectory { get; set; } + [Required] + public string TestingPlatform { get; set; } + [Required] public string SampleSolutionDirectory { get; set; } @@ -145,6 +148,9 @@ void SetResultsDirectory(ITaskItem2 item) this.BinDirectory, item.GetMetadata("ProjectName"), $"{configuration}_{targetFramework}", + this.TestingPlatform is "MicrosoftTestingPlatform" + ? "TestResults" + : "", "allure-results" ); item.SetMetadataValueLiteral("ResultsDirectory", defaultResultsDirectory); diff --git a/build/Allure.Build.Tasks/Functions/Files.cs b/build/Allure.Build.Tasks/Functions/Files.cs index c1a8cb5c..526275d3 100644 --- a/build/Allure.Build.Tasks/Functions/Files.cs +++ b/build/Allure.Build.Tasks/Functions/Files.cs @@ -60,6 +60,7 @@ public static GeneratedFileSource DirectoryBuildProps( string solutionDir, string targetFrameworks, string outputType, + IEnumerable<(string name, string value)> properties, IEnumerable imports, IEnumerable packages, IEnumerable projects, @@ -70,6 +71,7 @@ IEnumerable analyzerProjects imports: imports, targetFrameworks: targetFrameworks, outputType: outputType, + properties: properties, packages: packages, projects: projects, analyzerProjects: analyzerProjects diff --git a/build/Allure.Build.Tasks/Functions/XmlDefinitions.cs b/build/Allure.Build.Tasks/Functions/XmlDefinitions.cs index a70ddf0d..64330227 100644 --- a/build/Allure.Build.Tasks/Functions/XmlDefinitions.cs +++ b/build/Allure.Build.Tasks/Functions/XmlDefinitions.cs @@ -106,6 +106,7 @@ public static XDocument DirectoryBuildProps( IEnumerable imports, string targetFrameworks, string outputType, + IEnumerable<(string name, string value)> properties, IEnumerable packages, IEnumerable projects, IEnumerable analyzerProjects @@ -120,7 +121,8 @@ IEnumerable analyzerProjects ("OutputType", outputType), ("EnableDefaultItems", "false"), ("IsTestProject", "true"), - ("AspectInjector_Enabled", "false") + ("AspectInjector_Enabled", "false"), + ..properties, ]), MsBuild.GetItemGroup("Compile", [("Include", Path.Combine("**", "*.cs"))]), MsBuild.GetItemGroup("Clean", [ diff --git a/build/Allure.Build.Tasks/GenerateSampleSolution.cs b/build/Allure.Build.Tasks/GenerateSampleSolution.cs index 7bca0e93..b92a797f 100644 --- a/build/Allure.Build.Tasks/GenerateSampleSolution.cs +++ b/build/Allure.Build.Tasks/GenerateSampleSolution.cs @@ -22,6 +22,9 @@ public class GenerateSampleSolution : Task [Required] public ITaskItem[] SampleProjectReferences { get; set; } + [Required] + public ITaskItem[] SampleProperties { get; set; } + [Required] public string ProjectDirectory { get; set; } @@ -50,6 +53,8 @@ public class GenerateSampleSolution : Task IEnumerable SampleSources2 => this.SampleSources.Cast(); + IEnumerable SampleProperties2 => this.SampleProperties.Cast(); + IEnumerable SamplePackageReferences2 => this.SamplePackageReferences.Cast(); @@ -125,6 +130,9 @@ GeneratedFileSource PrepareDirectoryBuildProps(MsBuildImportFiles imports) => solutionDir: this.SampleSolutionDir, targetFrameworks: this.SampleTargetFrameworks, outputType: Projects.ResolveOutputType(this.TestingPlatform), + properties: this.SampleProperties2.Select( + (item) => (item.EvaluatedIncludeEscaped, item.GetMetadataValueEscaped("Value")) + ), imports.PropsFiles, packages: this.CommonPackageNames, projects: this.SampleProjectReferences.Select((item) => diff --git a/src/Allure.TestingPlatform/Functions/ConfigurationFunctions.cs b/src/Allure.TestingPlatform/Functions/ConfigurationFunctions.cs index 3b9c964c..cb7a2944 100644 --- a/src/Allure.TestingPlatform/Functions/ConfigurationFunctions.cs +++ b/src/Allure.TestingPlatform/Functions/ConfigurationFunctions.cs @@ -48,14 +48,14 @@ public static AllureConfiguration ReadConfiguration( ); } + configPath ??= Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + AllureConstants.CONFIG_FILENAME + ); + JObject configJson = File.Exists(configPath) ? JObject.Parse(File.ReadAllText(configPath)) - : Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, - AllureConstants.CONFIG_FILENAME - ) is { } defaultConfigPath && File.Exists(defaultConfigPath) - ? JObject.Parse(File.ReadAllText(defaultConfigPath)) - : []; + : []; var allureSection = configJson["allure"] is { } allureObject ? allureObject diff --git a/src/Allure.TestingPlatform/Functions/ModelFunctionExtensions.cs b/src/Allure.TestingPlatform/Functions/ModelFunctionExtensions.cs index 946f50b8..8426b442 100644 --- a/src/Allure.TestingPlatform/Functions/ModelFunctionExtensions.cs +++ b/src/Allure.TestingPlatform/Functions/ModelFunctionExtensions.cs @@ -8,6 +8,7 @@ using Allure.Net.Commons.Configuration; using Allure.Net.Commons.Functions; using Allure.Net.Commons.Sdk; +using Allure.TestingPlatform.Sdk.Properties; using Microsoft.Testing.Platform.Extensions.Messages; namespace Allure.TestingPlatform.Functions; @@ -16,8 +17,7 @@ static class ModelFunctionExtensions { extension (ModelFunctions) { - public static TestResult CreateTestResult(AllureConfiguration config) => - new() + public static TestResult CreateTestResult(AllureConfiguration config) => new() { uuid = IdFunctions.CreateUUID(), labels = [ @@ -202,5 +202,11 @@ public static void AddGlobalAttachmentFile(IAllureResultsWriter writer, string? } ); } + + public static bool IsCancelled(TestResult result) => + result.labels.Any(IsCancellationMarker); + + public static bool IsCancellationMarker(Label label) => + label.name == AllureCancelProperty.CANCEL_LABEL_NAME && label.value == "true"; } } diff --git a/src/Allure.TestingPlatform/Registration/IStandaloneAllureRegistrationContext.cs b/src/Allure.TestingPlatform/Registration/IStandaloneAllureRegistrationContext.cs index 2bf07db8..2288c8a1 100644 --- a/src/Allure.TestingPlatform/Registration/IStandaloneAllureRegistrationContext.cs +++ b/src/Allure.TestingPlatform/Registration/IStandaloneAllureRegistrationContext.cs @@ -3,7 +3,6 @@ using Allure.Net.Commons; using Allure.Net.Commons.Configuration; using Allure.Net.Commons.Sdk; -using Microsoft.Testing.Platform.Logging; namespace Allure.TestingPlatform.Registration; diff --git a/src/Allure.TestingPlatform/Sdk/Properties/AllureCancelProperty.cs b/src/Allure.TestingPlatform/Sdk/Properties/AllureCancelProperty.cs new file mode 100644 index 00000000..f00fd914 --- /dev/null +++ b/src/Allure.TestingPlatform/Sdk/Properties/AllureCancelProperty.cs @@ -0,0 +1,21 @@ +using Allure.Net.Commons; +using Allure.TestingPlatform.Sdk.Runtime; + +namespace Allure.TestingPlatform.Sdk.Properties; + +/// +/// Cancels the test result precluding it from being written to the results directory. +/// +public sealed class AllureCancelProperty() : IAllureProperty +{ + /// + public void Apply(LiveAllureTestingPlatformRuntime _, TestResult target) + { + target.labels.Add(new(){ name = CANCEL_LABEL_NAME, value = "true" }); + } + + /// + /// The name of the marker label that signals the test result was cancelled. + /// + public const string CANCEL_LABEL_NAME = "__allure_cancelled__"; +} diff --git a/src/Allure.TestingPlatform/Sdk/TestingPlatformExtensions/AllureDataConsumer.cs b/src/Allure.TestingPlatform/Sdk/TestingPlatformExtensions/AllureDataConsumer.cs index af3c1f1a..122c5c38 100644 --- a/src/Allure.TestingPlatform/Sdk/TestingPlatformExtensions/AllureDataConsumer.cs +++ b/src/Allure.TestingPlatform/Sdk/TestingPlatformExtensions/AllureDataConsumer.cs @@ -180,17 +180,7 @@ async Task ConsumeTestNodeUpdateMessage(CorrelationUid correlationUid, TestNodeU runningTestContext = state.ForkContext(testContextUid, runningTestContext, this.StartTest); } - state.ReleaseContext( - testContextUid, - () => this.Lifecycle - .UpdateTestCase((testResult) => - { - this.ApplyProperties(testResult, node); - ApplyFallbacks(testResult, node); - }) - .StopTestCase() - .WriteTestCase() - ); + state.ReleaseContext(testContextUid, () => this.FinalizeTestResult(node)); } async Task ConsumeSessionFileArtifactMessage(SessionFileArtifact message) => @@ -337,4 +327,25 @@ static TestResult ApplyTestMethodIdentifierProperty(TestResult testResult, TestM ModelFunctions.ApplyIdentityAsFallback(testResult, identifierProperty); return testResult; } + + void FinalizeTestResult(TestNode node) + { + bool isCancelled = false; + this.Lifecycle.UpdateTestCase((testResult) => + { + this.ApplyProperties(testResult, node); + if (ModelFunctions.IsCancelled(testResult)) + { + isCancelled = true; + return; + } + ApplyFallbacks(testResult, node); + }); + + if (!isCancelled) + { + this.Lifecycle.StopTestCase(); + this.Lifecycle.WriteTestCase(); + } + } } diff --git a/src/Allure.Xunit.v3.Generator/Allure.Xunit.v3.Generator.csproj b/src/Allure.Xunit.v3.Generator/Allure.Xunit.v3.Generator.csproj new file mode 100644 index 00000000..064b710c --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Allure.Xunit.v3.Generator.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + Allure.Xunit.Generator + enable + Qameta Software Inc + + A source generator that automatically enables Allure.Xunit.v3 for a + Microsoft Testing Platform C# project and adds support for Allure test + plan pre-filtering. + + + true + + + + + false + false + + + + + + + diff --git a/src/Allure.Xunit.v3.Generator/AllureXunitGenerator.cs b/src/Allure.Xunit.v3.Generator/AllureXunitGenerator.cs new file mode 100644 index 00000000..73aeb458 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/AllureXunitGenerator.cs @@ -0,0 +1,542 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using Allure.Xunit.Generator.Constants; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Allure.Xunit.Generator; + + +/// +/// Generates Allure.Xunit.v3 assembly setup, entry point helpers, and test plan +/// pre-filtering support for xUnit.net v3 projects that run with Microsoft Testing Platform. +/// +[Generator] +public sealed class AllureXunitGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var options = SetupGeneratorOptionsStream(context); + var hasTestHook = SetupHasAllureXunitAttributeStream(context); + var selfRegistration = SetupSelfRegistrationStream(context); + var allureIdMethods = SetupAllureIdMethodStream(context); + + context.RegisterPostInitializationOutput(GenerateAllureXunitRunner); + context.RegisterSourceOutput(options.Combine(hasTestHook), GenerateAllureXunitAssemblyAttribute); + context.RegisterSourceOutput(options.Combine(selfRegistration), GenerateAllureXunitEntryPoint); + context.RegisterSourceOutput(allureIdMethods, GenerateAllureXunitTestPlan); + } + + static IncrementalValueProvider SetupSelfRegistrationStream( + IncrementalGeneratorInitializationContext context + ) => + context.AnalyzerConfigOptionsProvider + .Select(ResolveSelfRegisteredExtensionsTypeName) + .Combine(context.CompilationProvider) + .Select(VerifySelfRegistration); + + static string ResolveSelfRegisteredExtensionsTypeName( + AnalyzerConfigOptionsProvider optionsProvider, + CancellationToken token + ) => + optionsProvider.GlobalOptions + .TryGetValue(Options.RootNamespace, out var rootNamespace) + && !string.IsNullOrEmpty(rootNamespace) + ? $"{rootNamespace}.{TypeNames.SelfRegisteredExtensions}" + : TypeNames.SelfRegisteredExtensions; + + static IncrementalValueProvider> SetupAllureIdMethodStream( + IncrementalGeneratorInitializationContext context + ) => + context.SyntaxProvider + .CreateSyntaxProvider( + predicate: IsMethodWithAttributes, + transform: ToAllureIdMethodKeyValuePair + ) + .Where(static (kv) => kv is not null) + .Select(static (kv, _) => kv!.Value) + .Collect(); + + static bool IsMethodWithAttributes(SyntaxNode node, CancellationToken _) => + node is MethodDeclarationSyntax methodDeclaration + && methodDeclaration.AttributeLists.Count > 0; + + static (int, string)? ToAllureIdMethodKeyValuePair(GeneratorSyntaxContext ctx, CancellationToken _) + { + var semanticModel = ctx.SemanticModel; + var methodDeclaration = (MethodDeclarationSyntax)ctx.Node; + + var allureIdAttributeType = semanticModel.Compilation.GetTypeByMetadataName(Types.AllureIdAttribute); + if (allureIdAttributeType is null) + { + return null; + } + + foreach (var attributeList in methodDeclaration.AttributeLists) + { + foreach (var attributeApplicationSyntax in attributeList.Attributes) + { + if (semanticModel.GetSymbolInfo(attributeApplicationSyntax).Symbol is not IMethodSymbol attributeApplication) + { + continue; + } + + var attributeType = attributeApplication.ContainingType; + + if (SymbolEqualityComparer.Default.Equals(attributeType, allureIdAttributeType)) + { + var method = semanticModel.GetDeclaredSymbol(methodDeclaration); + if (method is not null) + { + foreach (var attributeData in method.GetAttributes()) + { + if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, allureIdAttributeType)) + { + continue; + } + + var allureIdObj = attributeData.ConstructorArguments[0].Value; + if (allureIdObj is int allureId) + { + var typeFullName = method.ContainingType.ToDisplayString(FullyQualifiedNoTypeParameters); + var methodName = method.Name; + return (allureId, $"{typeFullName}.{methodName}"); + } + } + } + + return null; + } + } + } + + return null; + } + + static string VerifySelfRegistration( + (string selfRegistrationTypeName, Compilation compilation) input, + CancellationToken token + ) + { + var (selfRegistrationTypeName, compilation) = input; + + var builderType = compilation.GetTypeByMetadataName( + "Microsoft.Testing.Platform.Builder.ITestApplicationBuilder" + ); + if (builderType is null) + { + return ""; + } + + var selfRegisteredExtensionsType = + compilation.GetTypeByMetadataName(selfRegistrationTypeName); + + if (selfRegisteredExtensionsType is null) + { + return ""; + } + + foreach (var member in selfRegisteredExtensionsType.GetMembers(MethodNames.AddSelfRegisteredExtensions)) + { + if (ToRegistrationMethod(builderType, member) is { } method) + { + return GetMethodGroupExpression(method); + } + } + + return ""; + } + + static IMethodSymbol? ToRegistrationMethod(INamedTypeSymbol builderType, ISymbol? member) => + member is IMethodSymbol + { + IsStatic: true, + IsExtensionMethod: true, + ReturnsVoid: true, + DeclaredAccessibility: Accessibility.Public, + Parameters: { Length: 2 } parameters, + } method + && SymbolEqualityComparer.Default.Equals(parameters[0].Type, builderType) + && parameters[1].Type is IArrayTypeSymbol + { + ElementType.SpecialType: SpecialType.System_String, + } + ? method + : null; + + + static IncrementalValueProvider SetupGeneratorOptionsStream( + IncrementalGeneratorInitializationContext context + ) => + context + .AnalyzerConfigOptionsProvider + .Select(ReadGeneratorProperties); + + static IncrementalValueProvider SetupHasAllureXunitAttributeStream( + IncrementalGeneratorInitializationContext context + ) => + context + .CompilationProvider + .Select(HasAllureXunitAttribute); + + static AllureXunitGeneratorOptions ReadGeneratorProperties( + AnalyzerConfigOptionsProvider provider, + CancellationToken _ + ) + { + var opts = provider.GlobalOptions; + return new AllureXunitGeneratorOptions( + GenerateEntryPoint: ReadBooleanProperty(opts, Options.GenerateEntryPoint), + ApplyAttribute: ReadBooleanProperty(opts, Options.ApplyAttribute) + ); + + } + + static bool ReadBooleanProperty(AnalyzerConfigOptions opts, string key) => + !opts.TryGetValue(key, out var generateEntryPoint) + || generateEntryPoint.Equals("true", StringComparison.InvariantCultureIgnoreCase); + + static string GetMethodGroupExpression(IMethodSymbol method) => + $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}"; + + static bool HasAllureXunitAttribute(Compilation compilation, CancellationToken _) + { + var attributeType = + compilation.GetTypeByMetadataName(Types.AllureXunitAttribute); + + if (attributeType is null) + { + return false; + } + + foreach (var attr in compilation.Assembly.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeType)) + { + return true; + } + } + + return false; + } + + static void GenerateAllureXunitRunner( + IncrementalGeneratorPostInitializationContext ctx + ) + { + ctx.AddSource("AllureXunitRunner.g.cs", AllureXunitRunnerSource); + } + + static void GenerateAllureXunitAssemblyAttribute( + SourceProductionContext ctx, + (AllureXunitGeneratorOptions, bool) input + ) + { + var (options, hasAllureXunit) = input; + if (!options.ApplyAttribute || hasAllureXunit) + { + return; + } + + ctx.AddSource( + "AllureXunitAssemblyAttributes.g.cs", + $$""" + {{Preamble}} + + [assembly:{{FqTypes.AllureXunitAttribute}}] + """ + ); + } + + static void GenerateAllureXunitEntryPoint( + SourceProductionContext ctx, + (AllureXunitGeneratorOptions, string) input + ) + { + var (options, selfRegistrationMethod) = input; + + if (!options.GenerateEntryPoint) + { + return; + } + + ctx.AddSource("AllureXunitEntryPoint.g.cs", GetEntryPointSource(selfRegistrationMethod)); + } + + static void GenerateAllureXunitTestPlan( + SourceProductionContext ctx, + ImmutableArray<(int, string)> allureIdMethods + ) + { + ctx.AddSource("AllureXunitTestPlan.g.cs", GetAllureXunitTestPlanSource(allureIdMethods)); + } + + static string GetAllureXunitTestPlanSource(ImmutableArray<(int, string)> allureIdMethods) + { + var sb = new StringBuilder( + $$""" + {{Preamble}} + namespace Allure.Xunit + { + [{{FqTypes.ExcludeFromCodeCoverage}}] + internal static class AllureXunitTestPlan + { + /// + /// Returns a new array that includes the original arguments plus + /// --filter-method xUnit arguments for each test method + /// selected by the current test plan. + /// + /// + /// The original command-line arguments passed to the test application. + /// + /// + /// A new array that contains followed by + /// the xUnit.net pre-execution filter arguments for the current Allure test plan. + /// + public static string[] {{MethodNames.AddXunitPreExecutionFilterArguments}}(string[] originalArguments) + { + return {{FqTypes.TestPlanFunctions}}.{{MethodNames.AddXunitPreExecutionFilterArguments}}(originalArguments, AllureIdRegistry); + } + + """ + ); + + AddAllureIdRegistry(sb, allureIdMethods); + + sb.AppendLine( + """ + } + } + """ + ); + + return sb.ToString(); + } + + static void AddAllureIdRegistry(StringBuilder sb, ImmutableArray<(int, string)> entries) + { + sb.AppendLine( + $$""" + + /// + /// Gets a mapping from Allure ID values to the fully qualified names of methods annotated + /// with using those IDs. + /// + /// + /// Method names include the namespace, containing type name, and method name. + /// + internal static {{FqTypes.ImmutableDictionary_Int_ImmutableArray_String}} AllureIdRegistry { get; } + + static AllureXunitTestPlan() + { + {{FqTypes.ImmutableDictionaryBuilder_Int_ImmutableArray_String}} builder = {{FqMethods.ImmutableDictionary_CreateBuilder_Int_String}}(); + """ + ); + + foreach (var (allureId, methodName) in entries) + { + sb.AppendLine( + $$""" + AddAllureIdMethodEntry(builder, {{allureId}}, {{SymbolDisplay.FormatLiteral(methodName, quote: true)}}); + """ + ); + } + + sb.AppendLine( + $$""" + AllureIdRegistry = builder.ToImmutable(); + } + + static void AddAllureIdMethodEntry({{FqTypes.ImmutableDictionaryBuilder_Int_ImmutableArray_String}} builder, int allureId, string methodName) + { + {{FqTypes.ImmutableArray_String}} value; + builder[allureId] = builder.TryGetValue(allureId, out value) + ? value.Add(methodName) + : {{FqMethods.ImmutableArray_Create}}(methodName); + } + """ + ); + } + + static string AllureXunitRunnerSource => + $$""" + {{Preamble}} + namespace Allure.Xunit + { + /// + /// Defines a helper for running xUnit.net v3 with Allure from a custom entry point. + /// + [{{FqTypes.ExcludeFromCodeCoverage}}] + internal static class AllureXunitRunner + { + /// + /// Applies the test plan pre-execution filter and calls xUnit's + /// with an + /// explicit extension registration function. + /// + /// + /// A function that registers Microsoft Testing Platform extensions. The function must call + /// in order to enable Allure. + /// + /// The command-line arguments passed to the test application. + /// The xUnit.net process exit code. + public static async {{FqTypes.Task_Int}} RunAsync( + {{FqTypes.Action(FqTypes.ITestApplicationBuilder, "string[]")}} extensionRegistration, + string[] args + ) + { + return await {{FqTypes.TestPlatformTestFramework}}.RunAsync( + {{FqMethods.AddXunitPreExecutionFilterArguments}}(args), + extensionRegistration + ); + } + } + } + """; + + static string GetEntryPointSource(string addSelfRegisteredExtensions) + { + var selfRegistrationExists = addSelfRegisteredExtensions is { Length: >0 }; + + var sb = new StringBuilder( + $$""" + {{Preamble}} + + namespace Allure.Xunit + { + /// + /// Defines the functions for running xUnit.net v3 with Allure and other self-registered + /// Microsoft Testing Platform extensions enabled. + /// + [{{FqTypes.ExcludeFromCodeCoverage}}] + internal class AllureXunitEntryPoint + { + /// + /// Applies the test plan pre-execution filter and calls xUnit's + /// + /// with the self-registered Microsoft Testing Platform extensions. + /// + /// + /// If the arguments include -automated or @@, delegates to + /// , + /// which does not register Microsoft Testing Platform extensions. + /// Allure does not run in that case. + ///
+ /// Otherwise, if Allure self-registration is enabled, it will be registered + /// with the default settings. + ///
+ /// The command-line arguments passed to the test application. + /// The xUnit.net process exit code. + public static async {{FqTypes.Task_Int}} Main(string[] args) + { + if ({{FqMethods.Enumerable_Any}}(args, arg => arg == "-automated" || arg == "@@")) + return await {{FqMethods.ConsoleRunner_Run}}(args); + else + + """ + ); + + if (selfRegistrationExists) + { + sb.AppendLine( + $$""" + return await {{FqMethods.AllureXunitRunner_RunAsync}}({{addSelfRegisteredExtensions}}, args); + """ + ); + } + else + { + sb.AppendLine( + $$""" + { + {{FqMethods.Error_WriteLine}}("{{Messages.SelfRegistrationNotFound}}"); + return 1; + } + """ + ); + } + + sb.AppendLine( + $$""" + } + + /// + /// Applies the test plan pre-execution filter and calls xUnit's + /// with the + /// self-registered Microsoft Testing Platform extensions, then enables Allure + /// with a custom registration function. + /// + /// + /// If you call this function, make sure Allure.Xunit.v3 self-registration is disabled + /// (the 'Allure_XunitEnableSelfRegistration' MSBuild property is set to 'false'). + /// + /// + /// A function that configures Allure.Xunit.v3. + /// + /// The command-line arguments passed to the test application. + /// The xUnit.net process exit code. + public static async {{FqTypes.Task_Int}} RunAsync( + {{FqTypes.Action(FqTypes.IStandaloneAllureRegistrationContext)}} allureRegistration, + string[] args + ) + { + """ + ); + + if (selfRegistrationExists) + { + sb.AppendLine( + $$""" + return await {{FqMethods.AllureXunitRunner_RunAsync}}((builder, args) => + { + {{addSelfRegisteredExtensions}}(builder, args); + {{FqMethods.AddAllureXunit}}(builder, allureRegistration); + }, args); + """ + ); + } + else + { + sb.AppendLine( + $$""" + {{FqMethods.Error_WriteLine}}("{{Messages.SelfRegistrationNotFound}}"); + return 1; + """ + ); + } + + sb.AppendLine( + $$""" + } + } + } + """ + ); + + return sb.ToString(); + } + + static string Preamble => + $$""" + // + // This file was generated by {{typeof(AllureXunitGenerator).AssemblyQualifiedName}}. + // Do not edit this file manually; changes may be overwritten. + // + """; + + static readonly SymbolDisplayFormat FullyQualifiedNoTypeParameters = new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces + ); + + readonly record struct AllureXunitGeneratorOptions( + bool GenerateEntryPoint, + bool ApplyAttribute + ); +} diff --git a/src/Allure.Xunit.v3.Generator/CompilerHints/IsExternalInit.cs b/src/Allure.Xunit.v3.Generator/CompilerHints/IsExternalInit.cs new file mode 100644 index 00000000..fc1fbfd0 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/CompilerHints/IsExternalInit.cs @@ -0,0 +1,8 @@ +using System.ComponentModel; + +#pragma warning disable IDE0130 + +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit { } diff --git a/src/Allure.Xunit.v3.Generator/CompilerHints/NotNullWhen.cs b/src/Allure.Xunit.v3.Generator/CompilerHints/NotNullWhen.cs new file mode 100644 index 00000000..463e4708 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/CompilerHints/NotNullWhen.cs @@ -0,0 +1,9 @@ +#pragma warning disable IDE0130 + +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute +{ + public bool ReturnValue { get; } = returnValue; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/FqMethods.cs b/src/Allure.Xunit.v3.Generator/Constants/FqMethods.cs new file mode 100644 index 00000000..a0f9631e --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/FqMethods.cs @@ -0,0 +1,20 @@ +namespace Allure.Xunit.Generator.Constants; + +static class FqMethods +{ + public const string ConsoleRunner_Run = "global::Xunit.Runner.InProc.SystemConsole.ConsoleRunner.Run"; + + public const string Error_WriteLine = "global::System.Console.Error.WriteLine"; + + public const string Enumerable_Any = "global::System.Linq.Enumerable.Any"; + + public const string AddAllureXunit = "global::Allure.Xunit.AllureXunitExtensions.AddAllureXunit"; + + public const string ImmutableArray_Create = $"{FqTypes.ImmutableArray}.Create"; + + public const string ImmutableDictionary_CreateBuilder_Int_String = $"global::System.Collections.Immutable.ImmutableDictionary.CreateBuilder"; + + public const string AddXunitPreExecutionFilterArguments = $"global::Allure.Xunit.AllureXunitTestPlan.{MethodNames.AddXunitPreExecutionFilterArguments}"; + + public const string AllureXunitRunner_RunAsync = $"global::Allure.Xunit.AllureXunitRunner.RunAsync"; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/FqTypes.cs b/src/Allure.Xunit.v3.Generator/Constants/FqTypes.cs new file mode 100644 index 00000000..9a7055a9 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/FqTypes.cs @@ -0,0 +1,37 @@ +namespace Allure.Xunit.Generator.Constants; + +static class FqTypes +{ + public const string ITestApplicationBuilder = $"global::{Types.ITestApplicationBuilder}"; + + public const string AllureXunitAttribute = $"global::{Types.AllureXunitAttribute}"; + + public const string ExcludeFromCodeCoverage = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"; + + public const string TestPlatformTestFramework = "global::Xunit.MicrosoftTestingPlatform.TestPlatformTestFramework"; + + public const string IStandaloneAllureRegistrationContext = "global::Allure.TestingPlatform.Registration.IStandaloneAllureRegistrationContext"; + + public const string Task_Int = "global::System.Threading.Tasks.Task"; + + public const string TestPlanFunctions = "global::Allure.Xunit.Functions.TestPlanFunctions"; + + public const string ImmutableArray = $"global::System.Collections.Immutable.ImmutableArray"; + + public const string ImmutableArray_String = $"{ImmutableArray}"; + + public const string ImmutableDictionary = $"global::System.Collections.Immutable.ImmutableDictionary"; + + public const string ImmutableDictionary_Int_ImmutableArray_String = $"{ImmutableDictionary}"; + + public const string ImmutableDictionaryBuilder_Int_ImmutableArray_String = $"{ImmutableDictionary_Int_ImmutableArray_String}.Builder"; + + public static string Action(string argumentType) => + $"global::System.Action<{argumentType}>"; + + public static string Action(string argument1Type, string argument2Type) => + $"global::System.Action<{argument1Type}, {argument2Type}>"; + + public static string AllureIdAttribute = "global::Allure.Net.Commons.Attributes.AllureIdAttribute"; + +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/Messages.cs b/src/Allure.Xunit.v3.Generator/Constants/Messages.cs new file mode 100644 index 00000000..9f2abb57 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/Messages.cs @@ -0,0 +1,10 @@ +namespace Allure.Xunit.Generator.Constants; + +static class Messages +{ + public const string SelfRegistrationNotFound = + $"[Allure.Xunit.v3]: Could not find the {TypeNames.SelfRegisteredExtensions} " + + $"class with the {MethodNames.AddSelfRegisteredExtensions} method in " + + "the root namespace of the project. Make sure the Microsoft.Testing.Platform " + + "extension self-registration is enabled and try again."; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/MethodNames.cs b/src/Allure.Xunit.v3.Generator/Constants/MethodNames.cs new file mode 100644 index 00000000..e0dafc79 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/MethodNames.cs @@ -0,0 +1,8 @@ +namespace Allure.Xunit.Generator.Constants; + +static class MethodNames +{ + public const string AddXunitPreExecutionFilterArguments = "AddXunitPreExecutionFilterArguments"; + + public const string AddSelfRegisteredExtensions = "AddSelfRegisteredExtensions"; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/Options.cs b/src/Allure.Xunit.v3.Generator/Constants/Options.cs new file mode 100644 index 00000000..8554ec51 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/Options.cs @@ -0,0 +1,10 @@ +namespace Allure.Xunit.Generator.Constants; + +static class Options +{ + public const string RootNamespace = "build_property.RootNamespace"; + + public const string GenerateEntryPoint = "build_property.Allure_GenerateXunitEntryPoint"; + + public const string ApplyAttribute = "build_property.Allure_ApplyXunitAttribute"; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/SeeCrefs.cs b/src/Allure.Xunit.v3.Generator/Constants/SeeCrefs.cs new file mode 100644 index 00000000..8fd45eb4 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/SeeCrefs.cs @@ -0,0 +1,10 @@ +namespace Allure.Xunit.Generator.Constants; + +static class SeeCrefs +{ + public const string ConsoleRunner_Run = $"{FqMethods.ConsoleRunner_Run}(string[])"; + + public const string AddAllureXunit = + $"{FqMethods.AddAllureXunit}({FqTypes.ITestApplicationBuilder}, " + + $"global::System.Action{{{FqTypes.IStandaloneAllureRegistrationContext}}})"; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/TypeNames.cs b/src/Allure.Xunit.v3.Generator/Constants/TypeNames.cs new file mode 100644 index 00000000..6765801a --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/TypeNames.cs @@ -0,0 +1,6 @@ +namespace Allure.Xunit.Generator.Constants; + +static class TypeNames +{ + public const string SelfRegisteredExtensions = "SelfRegisteredExtensions"; +} diff --git a/src/Allure.Xunit.v3.Generator/Constants/Types.cs b/src/Allure.Xunit.v3.Generator/Constants/Types.cs new file mode 100644 index 00000000..0ee341f4 --- /dev/null +++ b/src/Allure.Xunit.v3.Generator/Constants/Types.cs @@ -0,0 +1,10 @@ +namespace Allure.Xunit.Generator.Constants; + +static class Types +{ + public const string ITestApplicationBuilder = "Microsoft.Testing.Platform.Builder.ITestApplicationBuilder"; + + public const string AllureXunitAttribute = "Allure.Xunit.AllureXunitAttribute"; + + public const string AllureIdAttribute = "Allure.Net.Commons.Attributes.AllureIdAttribute"; +} diff --git a/src/Allure.Xunit.v3/Allure.Xunit.v3.csproj b/src/Allure.Xunit.v3/Allure.Xunit.v3.csproj new file mode 100644 index 00000000..b284a7a6 --- /dev/null +++ b/src/Allure.Xunit.v3/Allure.Xunit.v3.csproj @@ -0,0 +1,54 @@ + + + + netstandard2.0 + Allure.Xunit + enable + Qameta Software Inc + Create beautiful reports from your xUnit.net v3 tests. + Allure-X-Color.png + $(PackageTags) xunit xunit3 xunit.v3 + + + + <_GeneratorTargetFramework>netstandard2.0 + <_GeneratorProjectName>Allure.Xunit.v3.Generator + <_GeneratorOutputPath>$( + [MSBuild]::NormalizePath( + '$(ArtifactsPath)', + 'bin', + '$(_GeneratorProjectName)', + '$(Configuration.ToLower())_$(_GeneratorTargetFramework)', + '$(_GeneratorProjectName).dll' + ) + ) + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Allure.Xunit.v3/AllureRunnerReporter.cs b/src/Allure.Xunit.v3/AllureRunnerReporter.cs new file mode 100644 index 00000000..69ca825b --- /dev/null +++ b/src/Allure.Xunit.v3/AllureRunnerReporter.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; +using Xunit.Runner.Common; +using Xunit.Sdk; +using Allure.Xunit.Internal; + +namespace Allure.Xunit; + +/// +/// Provides the xUnit.net v3 runner reporter that forwards test lifecycle messages +/// to Allure when the Allure Microsoft Testing Platform runtime is active. +/// +public class AllureRunnerReporter : IRunnerReporter +{ + /// + /// Initializes a new instance of the class. + /// + public AllureRunnerReporter() { } + + /// + public bool CanBeEnvironmentallyEnabled => AllureXunitMtpServices.IsAllureAlive; + + /// + public string Description => "Allure runner reporter for xUnit.net v3"; + + /// + public bool ForceNoLogo => false; + + /// + public bool IsEnvironmentallyEnabled => this.CanBeEnvironmentallyEnabled; + + /// + public string RunnerSwitch => "allure"; + + /// + public async ValueTask CreateMessageHandler( + IRunnerLogger logger, + IMessageSink? diagnosticMessageSink + ) + { + if (!AllureXunitMtpServices.IsAllureAlive) + { + return new DefaultRunnerReporterMessageHandler(logger); + } + + var allureHandler = new AllureMessageHandler(logger, AllureXunitMtpServices.MessageBus); + + CurrentMessageHandler = allureHandler; + + return allureHandler; + } + + internal static AllureMessageHandler? CurrentMessageHandler { get; private set; } +} diff --git a/src/Allure.Xunit.v3/AllureXunitAttribute.cs b/src/Allure.Xunit.v3/AllureXunitAttribute.cs new file mode 100644 index 00000000..a30e37df --- /dev/null +++ b/src/Allure.Xunit.v3/AllureXunitAttribute.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Xunit.Runner.Common; +using Xunit.v3; +using System.Reflection; +using Allure.TestingPlatform.Sdk.Correlation; +using Allure.Xunit.Internal; + +namespace Allure.Xunit; + +/// +/// Enables Allure.Xunit.v3 for an xUnit.net v3 test assembly. +/// +[AttributeUsage(AttributeTargets.Assembly)] +public class AllureXunitAttribute() : + Attribute, + IBeforeAfterTestAttribute, + IRegisterRunnerReporterAttribute, + ITraitAttribute +{ + /// + public Type RunnerReporterType => typeof(AllureRunnerReporter); + + /// + public void After(MethodInfo methodUnderTest, IXunitTest test) { } + + /// + public void Before(MethodInfo methodUnderTest, IXunitTest test) => + AllureRunnerReporter.CurrentMessageHandler?.HandleBeforeTest( + methodUnderTest, + test, + test.TestMethodArguments + ); + + /// + public IReadOnlyCollection> GetTraits() => + AllureXunitMtpServices.IsAllureAlive + ? [new(TestNodeMetadataCorrelationStrategy.MetadataKey, TestNodeMetadataCorrelationStrategy.CreateCorrelationUid())] + : []; +} diff --git a/src/Allure.Xunit.v3/AllureXunitBuilderHook.cs b/src/Allure.Xunit.v3/AllureXunitBuilderHook.cs new file mode 100644 index 00000000..657b96d0 --- /dev/null +++ b/src/Allure.Xunit.v3/AllureXunitBuilderHook.cs @@ -0,0 +1,21 @@ +using Microsoft.Testing.Platform.Builder; + +#pragma warning disable IDE0060 + +namespace Allure.Xunit; + +/// +/// Provides the Microsoft Testing Platform builder hook used by Allure.Xunit.v3 self-registration. +/// +public static class AllureXunitBuilderHook +{ + /// + /// Registers Allure.Xunit.v3 with the Microsoft Testing Platform test application builder. + /// + /// The test application builder to configure. + /// The command-line arguments passed to the test application. + public static void AddExtensions(ITestApplicationBuilder builder, string[] args) + { + builder.AddAllureXunit(); + } +} diff --git a/src/Allure.Xunit.v3/AllureXunitExtensions.cs b/src/Allure.Xunit.v3/AllureXunitExtensions.cs new file mode 100644 index 00000000..21f72725 --- /dev/null +++ b/src/Allure.Xunit.v3/AllureXunitExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Reflection; +using Allure.TestingPlatform.Registration; +using Allure.TestingPlatform.Sdk; +using Allure.Xunit.Internal; +using Microsoft.Testing.Platform.Builder; + +namespace Allure.Xunit; + +/// +/// Provides extension methods that register Allure.Xunit.v3 with Microsoft Testing Platform. +/// +public static class AllureXunitExtensions +{ + extension (ITestApplicationBuilder builder) + { + /// + /// Registers Allure.Xunit.v3 with Microsoft Testing Platform and applies + /// additional Allure.TestingPlatform configuration. + /// + /// + /// A callback that configures the Allure.TestingPlatform registration. + /// + public void AddAllureXunit(Action allureRegistration) + { + var allureRuntimeReferences = builder.AddEmbeddedAllure((ctx) => + { + allureRegistration(ctx); + if (IsAllureXunitAttributeApplied) + { + ctx.UseTestNodeMetadataCorrelation(); + } + }); + + builder.TestHost.AddTestHostApplicationLifetime((serviceProvider) => + new AllureXunitMtpServices( + serviceProvider, + allureRuntimeReferences.GetRuntimeReference(serviceProvider) + ) + ); + } + + /// + /// Registers Allure.Xunit.v3 with Microsoft Testing Platform using the default configuration. + /// + public void AddAllureXunit() => AddAllureXunit(builder, static (_) => { }); + } + + static bool IsAllureXunitAttributeApplied => + Assembly.GetEntryAssembly().GetCustomAttribute() is not null; +} diff --git a/src/Allure.Xunit.v3/CompilerHints/IsExternalInit.cs b/src/Allure.Xunit.v3/CompilerHints/IsExternalInit.cs new file mode 100644 index 00000000..fc1fbfd0 --- /dev/null +++ b/src/Allure.Xunit.v3/CompilerHints/IsExternalInit.cs @@ -0,0 +1,8 @@ +using System.ComponentModel; + +#pragma warning disable IDE0130 + +namespace System.Runtime.CompilerServices; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit { } diff --git a/src/Allure.Xunit.v3/CompilerHints/NotNullWhen.cs b/src/Allure.Xunit.v3/CompilerHints/NotNullWhen.cs new file mode 100644 index 00000000..463e4708 --- /dev/null +++ b/src/Allure.Xunit.v3/CompilerHints/NotNullWhen.cs @@ -0,0 +1,9 @@ +#pragma warning disable IDE0130 + +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute +{ + public bool ReturnValue { get; } = returnValue; +} diff --git a/src/Allure.Xunit.v3/Functions/ExceptionFunctions.cs b/src/Allure.Xunit.v3/Functions/ExceptionFunctions.cs new file mode 100644 index 00000000..d61e742d --- /dev/null +++ b/src/Allure.Xunit.v3/Functions/ExceptionFunctions.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; +using Allure.Xunit.Internal; +using Xunit.Sdk; + +namespace Allure.Xunit.Functions; + +static class ExceptionFunctions +{ + public static bool IsConfiguredAssertionFailure(ITestFailed testFailed) => + AllureXunitMtpServices.AllureRuntime is + { + Configuration.FailExceptions: { Count: >0 } failExceptions + } + && testFailed.ExceptionTypes.Any( + (e) => failExceptions.Contains(e, StringComparer.OrdinalIgnoreCase) + ); +} diff --git a/src/Allure.Xunit.v3/Functions/TestPlanFunctions.cs b/src/Allure.Xunit.v3/Functions/TestPlanFunctions.cs new file mode 100644 index 00000000..0b9b0ce1 --- /dev/null +++ b/src/Allure.Xunit.v3/Functions/TestPlanFunctions.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Allure.Net.Commons.Attributes; +using Allure.Net.Commons.Functions; +using Allure.Net.Commons.TestPlan; + +namespace Allure.Xunit.Functions; + +/// +/// Provides helpers used to apply Allure test plans to xUnit.net v3 test runs. +/// +public static class TestPlanFunctions +{ + static readonly Lazy testPlanLazy = new(AllureTestPlan.FromEnvironment); + + /// + /// Gets the global Allure test plan loaded from the current process environment. + /// + public static AllureTestPlan TestPlan => testPlanLazy.Value; + + /// + /// Returns an array consisting of the original CLI arguments plus filter arguments that, + /// when passed to xUnit.net v3, enforces the global test plan for the entry point assembly. + /// + /// An array of command-line arguments passed to the test application. + /// + /// A mapping from Allure ID to test method names. + /// + /// + /// A new array that contains followed by + /// the xUnit.net pre-execution filter arguments for the current Allure test plan. + /// + public static string[] AddXunitPreExecutionFilterArguments( + string[] originalArguments, + ImmutableDictionary> allureIdRegistry + ) => + [ + ..originalArguments, + ..GetXunitPreExecutionFilter(allureIdRegistry, TestPlan, Assembly.GetEntryAssembly()), + ]; + + /// + /// Returns a sequence of filter arguments that, when passed to xUnit.net v3, + /// enforces the provided test plan. + /// + /// + /// A mapping from Allure ID to test method names. + /// + /// + /// The test plan to enforce. Use to read + /// the global test plan, or use to get the cached instance. + /// + /// + /// The test assembly. In the Microsoft Testing Platform flow, this is the entry assembly. + /// + /// + /// A sequence of xunit.v3.mtp-v2 arguments in the form + /// --filter-method method1 --filter-method method2 .... + /// + public static IEnumerable GetXunitPreExecutionFilter( + ImmutableDictionary> allureIdRegistry, + AllureTestPlan testPlan, + Assembly testAssembly + ) + { + if (ReferenceEquals(testPlan, AllureTestPlan.DEFAULT_TESTPLAN)) + { + // No test plan provided. + yield break; + } + + bool emitted = false; + + var testAssemblyName = testAssembly.GetName().Name; + foreach (var entry in testPlan.Tests) + { + if (entry.Selector is not null + && TryMatchByFullName(testAssemblyName, entry.Selector, out var fullNameFilter)) + { + emitted = true; + yield return "--filter-method"; + yield return fullNameFilter; + } + + if (entry.Id is not null + && int.TryParse(entry.Id, out var expectedAllureId) + && allureIdRegistry.TryGetValue(expectedAllureId, out var allureIdFilters)) + { + foreach (var allureIdFilter in allureIdFilters) + { + emitted = true; + yield return "--filter-method"; + yield return allureIdFilter; + } + } + } + + if (!emitted) + { + // The test plan exists but is empty or no entry matched. + // No test should be run. + yield return "--filter-method"; + yield return NON_EXISTING_METHOD_NAME; + } + } + + /// + /// Checks if the test defined by the provided method is selected + /// by the global test plan. + /// + /// A test method to check. + /// + /// if the method is selected by the current test plan; + /// otherwise, . + /// + public static bool IsSelected(MethodInfo testMethod) => + TestPlan.IsSelected( + fullName: IdFunctions.CreateFullName(testMethod), + allureId: testMethod.GetCustomAttribute()?.Value + ); + + static bool TryMatchByFullName( + string testAssemblyName, + string fullName, + [NotNullWhen(true)] out string? filterMethodValue + ) + { + if (TrySplitAssemblyName(fullName, out var assemblyName, out var rest) + && StringComparer.OrdinalIgnoreCase.Equals(assemblyName, testAssemblyName) + && TryCropOutParameters(rest, out var methodFullName)) + { + filterMethodValue = methodFullName; + return true; + } + + filterMethodValue = default; + return false; + } + + static bool TrySplitAssemblyName( + string fullName, + [NotNullWhen(true)] out string assemblyName, + [NotNullWhen(true)] out string? rest + ) + { + if (fullName.IndexOf(':') is int assemblyNameEnd && assemblyNameEnd != -1) + { + assemblyName = fullName.Substring(0, assemblyNameEnd); + rest = fullName.Substring(assemblyNameEnd + 1); + return true; + } + + rest = assemblyName = ""; + return false; + } + + static bool TryCropOutParameters( + string methodSignature, + [NotNullWhen(true)] out string methodFullName + ) + { + int cropAt = methodSignature.IndexOf('('); + int typeParametersStart = methodSignature.IndexOf('['); + if (cropAt == -1 || typeParametersStart != -1 && typeParametersStart < cropAt) + { + cropAt = typeParametersStart; + } + + if (cropAt == -1) + { + methodFullName = ""; + return false; + } + + methodFullName = methodSignature.Substring(0, cropAt); + return true; + } + + const string NON_EXISTING_METHOD_NAME = "f78eccb2-459e-482b-87ea-d8d7106959d1"; +} diff --git a/src/Allure.Xunit.v3/Functions/XunitMessageMapping.cs b/src/Allure.Xunit.v3/Functions/XunitMessageMapping.cs new file mode 100644 index 00000000..6a6bcd54 --- /dev/null +++ b/src/Allure.Xunit.v3/Functions/XunitMessageMapping.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Allure.Net.Commons; +using Allure.TestingPlatform.Sdk.Correlation; +using Allure.TestingPlatform.Sdk.Messages; +using Allure.TestingPlatform.Sdk.Properties; +using Xunit.Runner.Common; +using Xunit.Sdk; + +using AllureTestResult = Allure.Net.Commons.TestResult; + +namespace Allure.Xunit.Functions; + +static class XunitMessageMapping +{ + public static bool TryGetCorrelationUid( + IReadOnlyDictionary> xunitTraits, + [NotNullWhen(true)] out CorrelationUid? correlationUid + ) + { + if (xunitTraits.TryGetValue(TestNodeMetadataCorrelationStrategy.MetadataKey, out var metadataValue) + && metadataValue.Count == 1 + ) + { + correlationUid = new(metadataValue.First()); + return true; + } + correlationUid = null; + return false; + } + + public static bool TryConvertToAllureScopeStartMessage( + ITestCaseStarting testCaseStarting, + [NotNullWhen(true)] out AllureScopeStartMessage? allureScopeStart + ) + { + if (testCaseStarting is { Traits: { } traits, TestCaseUniqueID: { } scopeUid } + && TryGetCorrelationUid(traits, out var correlationUid) + ) + { + allureScopeStart = new(correlationUid.Value, new(scopeUid)); + return true; + } + allureScopeStart = null; + return false; + } + + public static bool TryConvertToAllureScopeStopMessage( + ITestCaseFinished testCaseFinished, + MessageMetadataCache metadataCache, + [NotNullWhen(true)] out AllureScopeStopMessage? allureScopeStop + ) + { + if (testCaseFinished is { TestCaseUniqueID: { } scopeUid } + && metadataCache.TryGetTestCaseMetadata(scopeUid) is { Traits: { } traits } + && TryGetCorrelationUid(traits, out var correlationUid) + ) + { + allureScopeStop = new(correlationUid.Value, new(scopeUid)); + return true; + } + allureScopeStop = null; + return false; + } + + public static bool TryConvertToTestUpdateWithMethod( + MethodInfo testMethod, + ITest test, + object?[]? arguments, + [NotNullWhen(true)] out AllureTestUpdateMessage? allureTestUpdate + ) + { + if (TryGetCorrelationUid(test.Traits, out var correlationUid)) + { + allureTestUpdate = new AllureTestUpdateMessage(correlationUid.Value, new(test.TestCase.UniqueID)) + { + Properties = [ + new AllureTestMethodProperty(testMethod) { Arguments = [.. arguments ?? []] }, + new AllureDefaultSuitesProperty(testMethod.DeclaringType), + new AllureLabelsProperty([Label.Thread()]), + ] + }; + return true; + } + + allureTestUpdate = null; + return false; + } + + public static bool TryConvertToCancellation( + ITest test, + [NotNullWhen(true)] out AllureTestUpdateMessage? cancellation + ) + { + if (TryGetCorrelationUid(test.Traits, out var correlationUid)) + { + cancellation = new AllureTestUpdateMessage(correlationUid.Value, new(test.TestCase.UniqueID)) + { + Properties = [new AllureCancelProperty()], + }; + return true; + } + + cancellation = null; + return false; + } + + public static bool TryConvertToTestUpdateWithFailedStatus( + ITestFailed testFailed, + MessageMetadataCache metadataCache, + [NotNullWhen(true)] out AllureTestUpdateMessage? allureTestUpdate + ) + { + if (ExceptionFunctions.IsConfiguredAssertionFailure(testFailed) + && testFailed is { TestCaseUniqueID: { } testCaseUniqueId } + && metadataCache.TryGetTestCaseMetadata(testFailed.TestCaseUniqueID) is { Traits: { } traits } + && TryGetCorrelationUid(traits, out var correlationUid) + ) + { + allureTestUpdate = new AllureTestUpdateMessage(correlationUid.Value, new(testCaseUniqueId)) + { + Properties = [ + new AllureStatusProperty(Status.failed), + ] + }; + return true; + } + + allureTestUpdate = null; + return false; + } +} \ No newline at end of file diff --git a/src/Allure.Xunit.v3/Internal/AllureMessageHandler.cs b/src/Allure.Xunit.v3/Internal/AllureMessageHandler.cs new file mode 100644 index 00000000..adac6ec8 --- /dev/null +++ b/src/Allure.Xunit.v3/Internal/AllureMessageHandler.cs @@ -0,0 +1,115 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Allure.Net.Commons.TestPlan; +using Allure.TestingPlatform.Functions; +using Allure.TestingPlatform.Sdk.Messages; +using Allure.Xunit.Functions; +using Microsoft.Testing.Platform.Extensions.Messages; +using Xunit; +using Xunit.Runner.Common; +using Xunit.Sdk; +using IMtpMessageBus = Microsoft.Testing.Platform.Messages.IMessageBus; + +namespace Allure.Xunit.Internal; + +sealed class AllureMessageHandler( + IRunnerLogger logger, + IMtpMessageBus mtpMessageBus +) : + DefaultRunnerReporterMessageHandler(logger), + IRunnerReporterMessageHandler, + IDataProducer +{ + public Type[] DataTypesProduced => [ + typeof(AllureScopeStartMessage), + typeof(AllureScopeStopMessage), + typeof(AllureTestUpdateMessage), + ]; + + public string Uid { get; } = "30b8fdc2-68a8-49be-b0f8-b6bed05d07bd"; + + public string DisplayName { get; } = + "Allure.Xunit.v3 runner message handler"; + + public string Description { get; } = + "A message handler that translates xUnit runner reporter messages for Allure."; + + public string Version { get; } = TestingPlatformFunctions.GetPackageVersion(typeof(AllureMessageHandler)); + + public Task IsEnabledAsync() => Task.FromResult(true); + + protected override void HandleTestCaseStarting(MessageHandlerArgs args) + { + base.HandleTestCaseStarting(args); + + if (XunitMessageMapping.TryConvertToAllureScopeStartMessage(args.Message, out var allureScopeStart)) + { + this.PublishSync(allureScopeStart); + } + } + + internal void HandleBeforeTest(MethodInfo testMethod, ITest test, object?[]? arguments) + { + this.ApplyRuntimeGuard(testMethod, test); + + if (XunitMessageMapping.TryConvertToTestUpdateWithMethod( + testMethod, + test, + arguments, + out var allureTestUpdate + )) + { + this.PublishSync(allureTestUpdate); + } + } + + protected override void HandleTestFailed(MessageHandlerArgs args) + { + base.HandleTestFailed(args); + + if (XunitMessageMapping.TryConvertToTestUpdateWithFailedStatus( + args.Message, + this.MetadataCache, + out var allureTestUpdate + )) + { + this.PublishSync(allureTestUpdate); + } + } + + protected override void HandleTestCaseFinished(MessageHandlerArgs args) + { + base.HandleTestCaseFinished(args); + + if (XunitMessageMapping.TryConvertToAllureScopeStopMessage( + args.Message, + this.MetadataCache, + out var allureScopeStop + )) + { + this.PublishSync(allureScopeStop); + } + } + + void ApplyRuntimeGuard(MethodInfo testMethod, ITest test) + { + var isSelected = TestPlanFunctions.IsSelected(testMethod); + if (isSelected) + { + return; + } + + if (XunitMessageMapping.TryConvertToCancellation(test, out var cancellation)) + { + this.PublishSync(cancellation); + } + + Assert.Skip(AllureTestPlan.SkipReason); + } + + void PublishSync(AllureCorrelatedMessage message) => + mtpMessageBus + .PublishAsync(this, message) + .SpinWait(); +} diff --git a/src/Allure.Xunit.v3/Internal/AllureXunitMtpServices.cs b/src/Allure.Xunit.v3/Internal/AllureXunitMtpServices.cs new file mode 100644 index 00000000..20257a5d --- /dev/null +++ b/src/Allure.Xunit.v3/Internal/AllureXunitMtpServices.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Allure.TestingPlatform.Sdk; +using Allure.TestingPlatform.Sdk.Runtime; +using Microsoft.Testing.Platform.Extensions.TestHost; +using Microsoft.Testing.Platform.Messages; +using Microsoft.Testing.Platform.Services; + +namespace Allure.Xunit.Internal; + +class AllureXunitMtpServices( + IServiceProvider serviceProvider, + IAllureTestingPlatformRuntimeReference runtimeReference +) : + AllureTestingPlatformExtension( + "725e7e6e-74de-4e77-b0bf-eaebfe19914e", + "MTP services exposed to Allure.Xunit.v3", + "The extension represents a static access point for " + + "Allure.TestingPlatform and Microsoft Testing Platform " + + "services required by Allure.Xunit.v3", + runtimeReference + ), + ITestHostApplicationLifetime +{ + record class ExposedAllureMtpServices( + LiveAllureTestingPlatformRuntime AllureRuntime, + Lazy MessageBus + ); + + public Task BeforeRunAsync(CancellationToken cancellationToken) + { + allureTestingPlatform = new( + this.LiveRuntime, + new(serviceProvider.GetMessageBus) + ); + return Task.CompletedTask; + } + + public Task AfterRunAsync(int exitCode, CancellationToken cancellationToken) + { + allureTestingPlatform = null; + return Task.CompletedTask; + } + + static ExposedAllureMtpServices? allureTestingPlatform; + + static ExposedAllureMtpServices Services => allureTestingPlatform + ?? throw new InvalidOperationException( + "Internal error: Allure.Xunit.v3 is not initialized." + ); + + public static bool IsAllureAlive => allureTestingPlatform is not null; + + public static IMessageBus MessageBus => Services.MessageBus.Value; + + public static LiveAllureTestingPlatformRuntime AllureRuntime => Services.AllureRuntime; + +} diff --git a/src/Allure.Xunit.v3/Internal/Extensions.cs b/src/Allure.Xunit.v3/Internal/Extensions.cs new file mode 100644 index 00000000..61058adb --- /dev/null +++ b/src/Allure.Xunit.v3/Internal/Extensions.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Allure.Xunit.Internal; + +static class Extensions +{ + extension (Task task) + { + public void SpinWait() + { + var spin = new SpinWait(); + + while (!task.IsCompleted) + { + spin.SpinOnce(); + } + + task.GetAwaiter().GetResult(); + } + } + + extension (Task task) + { + public T SpinWait() + { + var spin = new SpinWait(); + + while (!task.IsCompleted) + { + spin.SpinOnce(); + } + + return task.GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/src/Allure.Xunit.v3/README.md b/src/Allure.Xunit.v3/README.md new file mode 100644 index 00000000..d4ea8a3a --- /dev/null +++ b/src/Allure.Xunit.v3/README.md @@ -0,0 +1,328 @@ +# Allure.Xunit.v3 + +[![Nuget release](https://img.shields.io/nuget/v/Allure.Xunit.v3?style=flat)](https://www.nuget.org/packages/Allure.Xunit.v3) +[![Nuget downloads](https://img.shields.io/nuget/dt/Allure.Xunit.v3?label=downloads&style=flat)](https://www.nuget.org/packages/Allure.Xunit.v3) + +> An Allure adapter for [xUnit.net v3](https://xunit.net/). + +[Allure Report logo](https://allurereport.org "Allure Report") + +- Learn more about Allure Report at [https://allurereport.org](https://allurereport.org) +- 📚 [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report +- ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community +- 📢 [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – stay updated with our latest news and updates +- 💬 [General Discussion](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community +- 🖥️ [Live Demo](https://demo.allurereport.org/) — explore a live example of Allure Report in action + +--- + +## Overview + +`Allure.Xunit.v3` integrates [Allure Report](https://allurereport.org/) with +[xUnit.net v3](https://xunit.net/) projects that run on +[Microsoft Testing Platform](https://learn.microsoft.com/en-us/dotnet/core/testing/microsoft-testing-platform-intro). + +The package builds on top of `Allure.TestingPlatform` and adds the xUnit.net v3 +adapter layer. It reports xUnit test results, Allure metadata attributes, +runtime API calls, parameters, steps, fixtures, and attachments to Allure result +files. + +`Allure.Xunit.v3` supports only xUnit.net v3 projects that run through +Microsoft Testing Platform. + +## Requirements + +Install these packages in the test project: + +- `xunit.v3.mtp-v2` +- `Allure.Xunit.v3` + +Enable the Microsoft Testing Platform runner: + +```xml + + true + + + + + + +``` + +If `UseMicrosoftTestingPlatformRunner` is not set to `true`, the adapter is not +registered and the project will not produce Allure results through this package. + +## Quick Start + +After the packages and MSBuild property are added, run the tests with `dotnet run`: + +```bash +dotnet run --project ./MyTests.csproj +``` + +If `dotnet test` is [configured to use Microsoft Testing Platform](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-dotnet-test#mtp-mode-of-dotnet-test) for your project, you can run: + +```bash +dotnet test +``` + +By default, Allure.Xunit.v3 writes result files to an `allure-results` +directory inside the Microsoft Testing Platform results directory. Microsoft +Testing Platform creates that directory as `TestResults` in the build output +directory unless you pass a different location with `--results-directory`. + +A typical default path is: + +``` +./bin/Debug/net10.0/TestResults/allure-results +``` + +Use attributes from `Allure.Net.Commons.Attributes` to add metadata to tests: + +```csharp +using Allure.Net.Commons.Attributes; +using Xunit; + +[AllureFeature("Checkout")] +public class CheckoutTests +{ + [Fact] + [AllureStory("Apply promo code")] + [AllureDescription("Checks that a valid promo code changes the order total.")] + public void AppliesPromoCode() + { + // ... + } +} +``` + +You can also use `Allure.Net.Commons.AllureApi` to add labels, links, +parameters, steps, and attachments at runtime. + +## Configuration + +The default registration uses the same configuration discovery as +`Allure.TestingPlatform`: + +1. explicit configuration passed during custom registration; +2. the file path from the `ALLURE_CONFIG` environment variable; +3. `allureConfig.json` from the application base directory; +4. default configuration values. + +An `allureConfig.json` file can be copied to the output directory: + +```xml + + + PreserveNewest + + +``` + +Example configuration: + +```json +{ + "directory": "allure-results", + "title": "My xUnit.net v3 tests" +} +``` + +The result directory can also be set for a single run: + +```bash +dotnet test -- --allure-results-directory ./artifacts/allure-results +``` + +## Test Plans + +To support test plans, Allure.Xunit.v3 generates an Allure-aware entry point. +The entry point adds xUnit.net pre-execution filter arguments for tests +selected by the current Allure test plan and calls xUnit.net. + +If `Allure_GenerateXunitEntryPoint` is set to `false`, the xUnit.net entry point +will be used. Pre-execution filtering is not generated. In that mode, +Allure still applies the test plan at runtime, but xUnit.net may construct +test class instances and run fixtures for tests that are not selected by the plan. + +## Advanced Usage + +### Custom Allure registration + +Disable the default Allure registration when you need to customize +`Allure.TestingPlatform`, for example to use a custom configuration object, +results writer, or enablement rule: + +```xml + + false + +``` + +Then define your own Microsoft Testing Platform builder hook: + +```csharp +using Allure.Net.Commons.Configuration; +using Allure.TestingPlatform; +using Allure.Xunit; +using Microsoft.Testing.Platform.Builder; + +namespace MyTests; + +public static class MyAllureBuilderHook +{ + public static void AddExtensions(ITestApplicationBuilder builder, string[] args) + { + builder.AddAllureXunit(allure => + { + allure.UseConfiguration(new AllureConfiguration + { + Directory = "artifacts/allure-results", + Title = "My xUnit.net v3 tests", + }); + }); + } +} +``` + +And register the hook for self-registration: + +```xml + + + My Allure registration + MyTests.MyAllureBuilderHook + + +``` + +Use a stable UUID value for `{UUID}`. + +### Custom entry point + +Use a custom entry point when the test application needs startup logic or when +Allure registration must be configured from code that runs before xUnit.net. + +If all you need is to provide custom configuration to Allure, delegate to +the generated `AllureXunitEntryPoint.RunAsync` overload: + +```csharp +using System.Threading.Tasks; +using Allure.Net.Commons.Configuration; +using Allure.TestingPlatform; +using Allure.Xunit; + +namespace MyTests; + +public static class Program +{ + public static async Task Main(string[] args) => + await AllureXunitEntryPoint.RunAsync(allure => + { + allure.UseConfiguration(new AllureConfiguration + { + Directory = "artifacts/allure-results", + }); + }, args); +} +``` + +Then set `StartupObject` to your entry point and disable the default Allure +registration: + +```xml + + false + MyTests.Program + +``` + +It's important to set `Allure_XunitEnableSelfRegistration` to `false`. Otherwise, +Allure will be registered twice, which results in an exception. + +For better control over extension registration, call `AllureXunitRunner.RunAsync` +instead and pass a registration function to it: + +```csharp +using System.Threading.Tasks; +using Allure.Net.Commons.Configuration; +using Allure.TestingPlatform; +using Allure.Xunit; + +namespace MyTests; + +public static class Program +{ + public static async Task Main(string[] args) => + await AllureXunitRunner.RunAsync( + (builder, argsWithTestPlanFilters) => + { + builder.AddAllureXunit(allure => + { + allure.UseConfiguration(new AllureConfiguration + { + Directory = "artifacts/allure-results", + }); + }); + // other registrations + }, + args + ); +} +``` + +Point `StartupObject` to the entry point class. You may also disable Allure entry point +generation: + +```xml + + false + MyTests.Program + +``` + +`StartupObject` is required in this setup because xUnit.net still generates its +own entry point. Omitting it will result in the `Program has more than one entry +point defined` error or your entry point not being selected. + +## MSBuild Properties + +| Property | Default | Description | +| --- | --- | --- | +| `UseMicrosoftTestingPlatformRunner` | user-defined | Must be set to `true` to run xUnit.net v3 through Microsoft Testing Platform. | +| `Allure_XunitEnableSelfRegistration` | `true` when MTP is enabled | Registers Allure.Xunit.v3 automatically with Microsoft Testing Platform. | +| `Allure_GenerateXunitEntryPoint` | `$(GenerateSelfRegisteredExtensions)` when MTP is enabled | Generates the Allure-aware xUnit.net entry point. | +| `Allure_ApplyXunitAttribute` | `true` when MTP is enabled | Applies `AllureXunitAttribute` to the test assembly automatically. | +| `Allure_WarnIfMtpDisabled` | `true` | Emits warning `ALLURE001` when the package is referenced without the MTP runner enabled. | +| `StartupObject` | `Allure.Xunit.AllureXunitEntryPoint` when an entry point is generated and `StartupObject` is empty | Selects the application entry point. | + +## Troubleshooting + +### No Allure results are generated + +Check that: + +- the project references both `xunit.v3.mtp-v2` and `Allure.Xunit.v3`; +- `UseMicrosoftTestingPlatformRunner` is set to `true`; +- `Allure_XunitEnableSelfRegistration` is not set to `false` unless you provide + a custom registration; +- the `--allure off` command-line option is not used. + +### Warning ALLURE001 is shown + +`Allure.Xunit.v3` was referenced by a project that does not enable the +Microsoft Testing Platform runner. Set `UseMicrosoftTestingPlatformRunner` to +`true`, or set `Allure_WarnIfMtpDisabled` to `false` if the warning is expected. + +### A custom entry point does not produce Allure results + +Make sure the entry point delegates to `AllureXunitEntryPoint.RunAsync` or +`AllureXunitRunner.RunAsync`, and make sure Allure is registered either through +self-registration or by calling `AddAllureXunit`. + +### Test plan correctly deselects the tests but class constructors and fixtures are still executed + +Make sure `Allure_GenerateXunitEntryPoint` is not set to `false` and that +`StartupObject`, when set, points to an entry point that delegates to the +Allure-generated runner helpers. diff --git a/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.props b/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.props new file mode 100644 index 00000000..3b8488ba --- /dev/null +++ b/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.props @@ -0,0 +1,13 @@ + + + + false + true + + + + + + + + \ No newline at end of file diff --git a/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.targets b/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.targets new file mode 100644 index 00000000..32bc1d53 --- /dev/null +++ b/src/Allure.Xunit.v3/package/buildTransitive/Allure.Xunit.v3.targets @@ -0,0 +1,56 @@ + + + + $(GenerateSelfRegisteredExtensions) + true + true + + + + + Allure.Xunit.v3 + Allure.Xunit.AllureXunitBuilderHook + + + + + + + + + + + + + + + Allure.Xunit.AllureXunitEntryPoint + + + + + \ No newline at end of file diff --git a/tests/Allure.Testing/AllureAssertionExtensions.cs b/tests/Allure.Testing/AllureAssertionExtensions.cs index 7509877f..fc1926e9 100644 --- a/tests/Allure.Testing/AllureAssertionExtensions.cs +++ b/tests/Allure.Testing/AllureAssertionExtensions.cs @@ -17,8 +17,8 @@ public static partial class AllureAssertionExtensions extension (IAssertionSource source) { /// - /// Checks if the exact number of test results were written to the output and each result satisfies the corresponding - /// constraints. + /// Checks if the exact number of test results were written to the output and + /// there is a perfect match between the test results and the provided constraints. /// /// /// Pass null or a function returning null for a no-op constraint. @@ -35,7 +35,7 @@ public MemberAssertionResult HasTestResults( var assertion = source.Member( s => s.TestResults, - tr => new CollectionItemConstraintsAssertion, AllureTestResult>( + tr => new CollectionItemConstraintsPerfectMatchAssertion, AllureTestResult>( tr.Context, constraints, "test result")); @@ -353,8 +353,8 @@ public NarrowCollectionByIndexAssertion, Allure } /// - /// Checks if the exact number of containers were written to the output and each container satisfies - /// the corresponding constraints. + /// Checks if the exact number of containers were written to the output and + /// there is a perfect match between the containers and the provided constraints. /// /// /// Pass null or a function returning null for a no-op constraint. @@ -371,7 +371,7 @@ public MemberAssertionResult HasContainers( var assertion = source.Member( s => s.Containers, - ca => new CollectionItemConstraintsAssertion, AllureContainer>( + ca => new CollectionItemConstraintsPerfectMatchAssertion, AllureContainer>( ca.Context, constraints, "container")); @@ -687,6 +687,147 @@ public NarrowCollectionByIndexAssertion, AllureC return new(source.Context.Map(ctx => ctx!.Containers), index, "container"); } + + /// + /// Checks if the exact number of globals were written to the output and + /// there is a perfect match between the globals and the provided constraints. + /// + /// + /// Pass null or a function returning null for a no-op constraint. + /// + public MemberAssertionResult HasGlobals( + Func, IAssertion?>?[] constraints, + [CallerArgumentExpression(nameof(constraints))] string? expression = null + ) + { + var ctx = source.Context; + var expressionBuilder = ctx.ExpressionBuilder; + expressionBuilder.Append($".{nameof(HasGlobals)}({expression ?? "..."})"); + var length = expressionBuilder.Length; + + var assertion = source.Member( + s => s.Globals, + globs => new CollectionItemConstraintsPerfectMatchAssertion, AllureGlobals>( + globs.Context, + constraints, + "globals")); + + expressionBuilder.Length = length; + + return assertion; + } + + /// + /// Checks if the output contains exactly one globals that matches the provided criteria. + /// + public MemberAssertionResult HasOnlyOneGlobals( + Func, IAssertion> criteria, + [CallerArgumentExpression(nameof(criteria))] string? expression = null + ) + { + var ctx = source.Context; + var expressionBuilder = ctx.ExpressionBuilder; + expressionBuilder.Append($".{nameof(HasOnlyOneGlobals)}({expression ?? "..."})"); + var length = expressionBuilder.Length; + + var assertion = source.Member( + s => s.Globals, + globs => new HasOneItemByCriteriaAssertion, AllureGlobals>( + globs.Context, + criteria, + "globals")); + + expressionBuilder.Length = length; + + return assertion; + } + + /// + /// Checks if the output contains at least one globals that matches the provided criteria. + /// + public MemberAssertionResult HasGlobals( + Func, IAssertion> criteria, + [CallerArgumentExpression(nameof(criteria))] string? expression = null + ) + { + var ctx = source.Context; + var expressionBuilder = ctx.ExpressionBuilder; + expressionBuilder.Append($".{nameof(HasGlobals)}({expression ?? "..."})"); + var length = expressionBuilder.Length; + + var assertion = source.Member( + s => s.Globals, + globs => new HasItemByCriteriaAssertion, AllureGlobals>( + globs.Context, + criteria, + "globals")); + + expressionBuilder.Length = length; + + return assertion; + } + + /// + /// Passes if no globals matches the provided criteria. + /// + public MemberAssertionResult HasNoGlobals( + Func, IAssertion> criteria, + [CallerArgumentExpression(nameof(criteria))] string? expression = null + ) + { + var ctx = source.Context; + var expressionBuilder = ctx.ExpressionBuilder; + expressionBuilder.Append($".{nameof(HasNoGlobals)}({expression ?? "..."})"); + var length = expressionBuilder.Length; + + var assertion = source.Member( + s => s.Globals, + globs => new HasNoItemByCriteriaAssertion, AllureGlobals>( + globs.Context, + criteria, + "globals")); + + expressionBuilder.Length = length; + + return assertion; + } + + /// + /// Checks if exactly one globals was written to the output and narrows the assertion chain to that result. + /// + public NarrowCollectionAssertion, AllureGlobals> HasSingleGlobals() + { + source.Context.ExpressionBuilder.Append($".{nameof(HasSingleGlobals)}()"); + + return new(source.Context.Map(ctx => ctx!.Globals), "globals"); + } + + /// + /// Checks if exactly one globals matches the provided criteria and narrows the assertion chain to that result. + /// + public NarrowCollectionByCriteriaAssertion, AllureGlobals> HasSingleGlobals( + Func, IAssertion> criteria, + [CallerArgumentExpression(nameof(criteria))] string? expression = null + ) + { + source.Context.ExpressionBuilder.Append($".{nameof(HasSingleGlobals)}({expression ?? "..."})"); + + return new(source.Context.Map(ctx => ctx!.Globals), criteria, "globals"); + } + + /// + /// Checks if enough globals were written to the output and narrows the assertion chain to the result + /// at the specified index. + /// + public NarrowCollectionByIndexAssertion, AllureGlobals> HasGlobalsAt( + int index, + [CallerArgumentExpression(nameof(index))] string? expression = null + ) + { + source.Context.ExpressionBuilder.Append($".{nameof(HasGlobalsAt)}({expression ?? "..."})"); + + return new(source.Context.Map(ctx => ctx!.Globals), index, "globals"); + } } extension (IAssertionSource> source) diff --git a/tests/Allure.Testing/AllureSampleRunner.cs b/tests/Allure.Testing/AllureSampleRunner.cs index d86f740f..dcce1a20 100644 --- a/tests/Allure.Testing/AllureSampleRunner.cs +++ b/tests/Allure.Testing/AllureSampleRunner.cs @@ -94,32 +94,90 @@ CancellationToken ct return allureResults; } - static async Task> EnsureSampleResults( - AllureSampleRegistryEntry sample, - AllureSampleRunInput input, + /// + /// Reads the Allure result files in the provided directory. + /// + /// A directory to read the files from. + public static async Task ReadAllureResults(string resultsDirectory) => + await ReadAllureResults(new DirectoryInfo(resultsDirectory), CancellationToken.None); + + /// + /// Reads the Allure result files in the provided directory. + /// + /// A directory to read the files from. + /// A cancellation token. + public static async Task ReadAllureResults( + string resultsDirectory, CancellationToken ct ) => - sample.IsPreRunFlow - ? EnsureExistingAllureResultsDirectory(sample) - : await ProduceSampleResults(sample, input, ct); + await ReadAllureResults(new DirectoryInfo(resultsDirectory), ct); - static DirectoryInfo EnsureExistingAllureResultsDirectory(AllureSampleRegistryEntry sample) + /// + /// Reads the Allure result files in the provided directory. + /// + /// A directory to read the files from. + public static async Task ReadAllureResults(DirectoryInfo resultsDirectory) => + await ReadAllureResults(resultsDirectory, CancellationToken.None); + + /// + /// Reads the Allure result files in the provided directory. + /// + /// A directory to read the files from. + /// A cancellation token. + public static async Task ReadAllureResults( + DirectoryInfo resultsDirectory, + CancellationToken ct + ) { - var path = sample.DefaultResultsPath; - var dInfo = new DirectoryInfo(path); - if (!dInfo.Exists || !dInfo.EnumerateFiles().Any()) + var resultFiles = resultsDirectory.Exists + ? resultsDirectory.GetFiles() + : []; + + var testResults = await ReadJsonObjectResults2(resultFiles, "-result.json", ct); + var containers = await ReadJsonObjectResults2(resultFiles, "-container.json", ct); + var globals = await ReadJsonObjectResults2(resultFiles, "-globals.json", ct); + + return (testResults, containers, globals) switch { - throw new FileNotFoundException( - $"Can't read Allure results of '{sample.RegistryId}.{sample.SampleId}'. " - + $"Please, make sure the sample's been run and the results are " - + $"available at '{path}'. Run " - + "'dotnet msbuild -t:Allure_RunTestSamples' to execute all the samples of " - + "the solution/project." - ); - } - return dInfo; + ({IsPassed: true, Value: var trs}, {IsPassed: true, Value: var conts}, { IsPassed: true, Value: var globs }) => new( + TestResults: trs, + Containers: conts, + Attachments: await ReadAttachments(resultFiles, ct), + Globals: globs + ), + + ({IsPassed: false, Message: var err1}, {IsPassed: false, Message: var err2}, { IsPassed: false, Message: var err3 }) => + throw new AssertionException($"{err1}{Environment.NewLine}{err2}{Environment.NewLine}{err3}"), + + ({IsPassed: false, Message: var err1}, {IsPassed: false, Message: var err2}, _) => + throw new AssertionException($"{err1}{Environment.NewLine}{err2}"), + + ({IsPassed: false, Message: var err1}, _, {IsPassed: false, Message: var err2}) => + throw new AssertionException($"{err1}{Environment.NewLine}{err2}"), + + (_, {IsPassed: false, Message: var err1}, {IsPassed: false, Message: var err2}) => + throw new AssertionException($"{err1}{Environment.NewLine}{err2}"), + + ({IsPassed: false, Message: var error}, _, _) => + throw new AssertionException(error), + + (_, {IsPassed: false, Message: var error}, _) => + throw new AssertionException(error), + + (_, _, {IsPassed: false, Message: var error}) => + throw new AssertionException(error), + }; } + static async Task> EnsureSampleResults( + AllureSampleRegistryEntry sample, + AllureSampleRunInput input, + CancellationToken ct + ) => + sample.IsPreRunFlow && input.IsPreRunCompatible + ? new DirectoryInfo(sample.DefaultResultsPath) + : await ProduceSampleResults(sample, input, ct); + static async Task> ProduceSampleResults( AllureSampleRegistryEntry sample, AllureSampleRunInput input, @@ -158,7 +216,9 @@ CancellationToken ct LogStdStreams(stdout, stderr); - return resultsDirGuard.Transfer(); + return resultsDirGuard.Own + ? resultsDirGuard.Transfer() + : resultsDirGuard.Value; } static void LogProcessStart(ProcessStartInfo psi, AllureSampleRunInput input) @@ -166,7 +226,7 @@ static void LogProcessStart(ProcessStartInfo psi, AllureSampleRunInput input) Console.WriteLine( "Running {0} {1}", psi.FileName, - string.Join(" ", psi.Arguments.Select(a => $"'{a}'")) + string.Join(" ", psi.ArgumentList.Select(a => $"'{a}'")) ); Console.WriteLine(" Working directory: {0}", psi.WorkingDirectory); @@ -370,35 +430,6 @@ static Task CollectProcessStream(StreamReader reader, CancellationToken TaskCreationOptions.LongRunning ); - static async Task ReadAllureResults( - DirectoryInfo resultsDirectory, - CancellationToken ct - ) - { - var resultFiles = resultsDirectory.GetFiles(); - - var testResults = await ReadJsonObjectResults2(resultFiles, "-result.json", ct); - var containers = await ReadJsonObjectResults2(resultFiles, "-container.json", ct); - - return (testResults, containers) switch - { - ({IsPassed: true, Value: var trs}, {IsPassed: true, Value: var conts}) => new( - TestResults: trs, - Containers: conts, - Attachments: await ReadAttachments(resultFiles, ct) - ), - - ({IsPassed: false, Message: var err1}, {IsPassed: false, Message: var err2}) => - throw new AssertionException($"{err1}{Environment.NewLine}{err2}"), - - ({IsPassed: false, Message: var error}, _) => - throw new AssertionException(error), - - (_, {IsPassed: false, Message: var error}) => - throw new AssertionException(error), - }; - } - static async Task>> ReadJsonObjectResults2( IEnumerable allOutputFiles, string suffix, diff --git a/tests/Allure.Testing/Assertions/CollectionItemConstraintsAssertion.cs b/tests/Allure.Testing/Assertions/CollectionItemConstraintsAssertion.cs index b61fc2f2..790f8592 100644 --- a/tests/Allure.Testing/Assertions/CollectionItemConstraintsAssertion.cs +++ b/tests/Allure.Testing/Assertions/CollectionItemConstraintsAssertion.cs @@ -44,8 +44,8 @@ await this.ApplyItemConstraints(items), protected override string GetExpectation() => this.lastExpectation is var (index, expectation) ? expectation is not null - ? $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} and {this.itemDescription} at {index} {expectation}" - : $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} and {this.itemDescription} at {index} satisfying the corresponding constraints" + ? $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} and {this.itemDescription} #{index + 1} {expectation}" + : $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} and {this.itemDescription} #{index + 1} satisfying the corresponding constraints" : $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} each satisfying the corresponding constraints"; async Task ApplyItemConstraints(IEnumerable items) @@ -80,7 +80,7 @@ async Task ApplyItemConstraints(IEnumerable items) this.lastExpectation = (i, expected); return AssertionResult.Failed( - $"{this.itemDescription} at {i} {actual}" + $"{this.itemDescription} #{i + 1} {actual}" ); } } diff --git a/tests/Allure.Testing/Assertions/CollectionItemConstraintsPerfectMatchAssertion.cs b/tests/Allure.Testing/Assertions/CollectionItemConstraintsPerfectMatchAssertion.cs new file mode 100644 index 00000000..366646bc --- /dev/null +++ b/tests/Allure.Testing/Assertions/CollectionItemConstraintsPerfectMatchAssertion.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Allure.Testing.Internal; +using TUnit.Assertions.Core; + +namespace Allure.Testing.Assertions; + +public class CollectionItemConstraintsPerfectMatchAssertion( + AssertionContext context, + Func, IAssertion?>?[] itemConstraints, + string itemDescription +) : + Assertion(context) + + where TCollection : IEnumerable +{ + readonly Func, IAssertion?>?[] itemConstraints = itemConstraints; + readonly string itemDescription = itemDescription; + readonly string itemDescriptionPlural = + itemConstraints.Length == 1 + ? itemDescription + : $"{itemDescription}s"; + + ImmutableArray? expectations = null; + + int ExpectedCount => this.itemConstraints.Length; + + protected override async Task CheckAsync( + EvaluationMetadata metadata + ) => + metadata switch + { + { Exception.Message: var message } => + await Task.FromResult(AssertionResult.Failed(message)), + + { Value: { } items } => + await this.ApplyItemConstraints(items), + + _ => await Task.FromResult(AssertionResult.Failed("the collection was null")), + }; + + protected override string GetExpectation() => + this.expectations is { } initiazedExpectations + ? $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} " + + $"perfectly matching the following constraints:" + + string.Join( + "", + initiazedExpectations.Select((e, ei) => + $"{Environment.NewLine} {ei + 1}. {e ?? $"a constraint #{ei + 1}"}" + ) + ) + : $"to have {this.itemConstraints.Length} {this.itemDescriptionPlural} " + + "perfectly matching the provided constraints"; + + async Task ApplyItemConstraints(IEnumerable items) + { + var list = items.ToImmutableArray(); + var actualCount = list.Length; + + if (actualCount != this.ExpectedCount) + { + return AssertionResult.Failed( + $"the collection had {actualCount} {this.itemDescriptionPlural}" + ); + } + + AssertionResult[,] matchMatrix = new AssertionResult[this.ExpectedCount, actualCount]; + + for (int i = 0; i < actualCount; i++) + { + var item = list[i]; + for (int j = 0; j < this.ExpectedCount; j++) + { + var constraint = this.itemConstraints[j]; + + matchMatrix[i, j] = constraint is not null + ? await AssertionFunctions.ExecuteInlineAssertionAsync( + item, + $"{this.itemDescription}s[{i}]", + constraint + ) + : AssertionResult.Passed; + } + } + + if (TryFindUnmatchedItems(matchMatrix) is { Length: >0 } mismatches) + { + this.expectations = mismatches[0].failures + .Select(e => e.expectation) + .ToImmutableArray(); + + return AssertionResult.Failed( + $"Couldn't find a perfect match:" + + string.Join( + "", + mismatches.Select((m) => + $"{Environment.NewLine} - {this.itemDescription} #{m.index + 1} didn't match" + + string.Join( + "", + m.failures.Select((f, ci) => + $"{Environment.NewLine} - constraint {ci + 1} because {f.actual}" + ) + ) + ) + ) + ); + } + + return AssertionResult.Passed; + } + + static ImmutableArray<(int index, ImmutableArray<(string? expectation, string? actual)> failures)> TryFindUnmatchedItems(AssertionResult[,] matches) + { + var itemCount = matches.GetLength(0); + var constraintCount = matches.GetLength(1); + + var itemByConstraint = Enumerable.Repeat(-1, constraintCount).ToArray(); + + return [.. Enumerable.Range(0, itemCount) + .Where((i) => !TryMatchItem(i, new bool[constraintCount])) + .Select((i) => (i, Enumerable.Range(0, constraintCount) + .Select((j) => NarrowingFunctions.ExtractExpectedAndActual(matches[i, j].Message, 2)) + .ToImmutableArray()))]; + + bool TryMatchItem(int item, bool[] seenConstraints) + { + for (var constraint = 0; constraint < constraintCount; constraint++) + { + if (!matches[item, constraint].IsPassed || seenConstraints[constraint]) + { + continue; + } + + seenConstraints[constraint] = true; + + var previousItem = itemByConstraint[constraint]; + + if (previousItem == -1 || TryMatchItem(previousItem, seenConstraints)) + { + itemByConstraint[constraint] = item; + return true; + } + } + + return false; + } + } +} diff --git a/tests/Allure.Testing/Assertions/JsonPropertyComparerAssertion.cs b/tests/Allure.Testing/Assertions/JsonPropertyComparerAssertion.cs index 0bf90549..9f739b13 100644 --- a/tests/Allure.Testing/Assertions/JsonPropertyComparerAssertion.cs +++ b/tests/Allure.Testing/Assertions/JsonPropertyComparerAssertion.cs @@ -33,7 +33,7 @@ await Task.FromResult(AssertionResult.Failed(message)), comparer.Equals(value, expectedValue) ? await Task.FromResult(AssertionResult.Passed) : await Task.FromResult( - AssertionResult.Failed($"received {FormatFunctions.FormatAsStringLiteral(value)}")), + AssertionResult.Failed($"received {propertyName} {FormatFunctions.FormatAsStringLiteral(value)}")), { Message: var message } => await Task.FromResult(AssertionResult.Failed(message)), diff --git a/tests/Allure.Testing/Assertions/Model/AllureAttachment.cs b/tests/Allure.Testing/Assertions/Model/AllureAttachment.cs index f49e4f01..9a4b3d31 100644 --- a/tests/Allure.Testing/Assertions/Model/AllureAttachment.cs +++ b/tests/Allure.Testing/Assertions/Model/AllureAttachment.cs @@ -1,13 +1,9 @@ using System.Text.Json; -using Allure.Testing.Assertions.Model.Properties; namespace Allure.Testing.Assertions.Model; public readonly record struct AllureAttachment(JsonElement Json) : - IAllureModelObject, - IAllureNameProperty, - IAllureAttachmentSourceProperty, - IAllureLinkTypeProperty + IAllureAttachment { public static string? Validate(JsonElement json) => default; diff --git a/tests/Allure.Testing/Assertions/Model/AllureGlobalAttachment.cs b/tests/Allure.Testing/Assertions/Model/AllureGlobalAttachment.cs new file mode 100644 index 00000000..ca2679e0 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/AllureGlobalAttachment.cs @@ -0,0 +1,13 @@ +using System.Text.Json; +using Allure.Testing.Assertions.Model.Properties; + +namespace Allure.Testing.Assertions.Model; + +public readonly record struct AllureGlobalAttachment(JsonElement Json) : + IAllureAttachment, + IAllureTimestampProperty +{ + public static string? Validate(JsonElement json) => default; + + public static AllureGlobalAttachment Constructor(JsonElement json) => new(json); +} diff --git a/tests/Allure.Testing/Assertions/Model/AllureGlobalError.cs b/tests/Allure.Testing/Assertions/Model/AllureGlobalError.cs new file mode 100644 index 00000000..90f5fe52 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/AllureGlobalError.cs @@ -0,0 +1,13 @@ +using System.Text.Json; +using Allure.Testing.Assertions.Model.Properties; + +namespace Allure.Testing.Assertions.Model; + +public readonly record struct AllureGlobalError(JsonElement Json) : + IAllureStatusDetails, + IAllureTimestampProperty +{ + public static string? Validate(JsonElement json) => default; + + public static AllureGlobalError Constructor(JsonElement json) => new(json); +} diff --git a/tests/Allure.Testing/Assertions/Model/AllureGlobals.cs b/tests/Allure.Testing/Assertions/Model/AllureGlobals.cs new file mode 100644 index 00000000..cda644d4 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/AllureGlobals.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using Allure.Testing.Assertions.Model.Properties; + +namespace Allure.Testing.Assertions.Model; + +public readonly record struct AllureGlobals(JsonElement Json) : + IAllureModelObject, + IAllureGlobalAttachmentsProperty, + IAllureGlobalErrorsProperty +{ + public static string? Validate(JsonElement json) => json switch + { + { ValueKind: JsonValueKind.Null } => + "the globals element was null", + + { ValueKind: not JsonValueKind.Object } => + "the globals element was not a JSON object", + + _ => null, + }; + + public static AllureGlobals Constructor(JsonElement json) => new(json); +} diff --git a/tests/Allure.Testing/Assertions/Model/AllureResults.cs b/tests/Allure.Testing/Assertions/Model/AllureResults.cs index ad57277e..c6a7b239 100644 --- a/tests/Allure.Testing/Assertions/Model/AllureResults.cs +++ b/tests/Allure.Testing/Assertions/Model/AllureResults.cs @@ -6,5 +6,6 @@ namespace Allure.Testing.Assertions.Model; public record class AllureResults( ImmutableArray TestResults, ImmutableArray Containers, - ImmutableDictionary> Attachments + ImmutableDictionary> Attachments, + ImmutableArray Globals ); diff --git a/tests/Allure.Testing/Assertions/Model/AllureStatusDetails.cs b/tests/Allure.Testing/Assertions/Model/AllureStatusDetails.cs index 9b57316b..f51cc482 100644 --- a/tests/Allure.Testing/Assertions/Model/AllureStatusDetails.cs +++ b/tests/Allure.Testing/Assertions/Model/AllureStatusDetails.cs @@ -1,15 +1,9 @@ using System.Text.Json; -using Allure.Testing.Assertions.Model.Properties; namespace Allure.Testing.Assertions.Model; public readonly record struct AllureStatusDetails(JsonElement Json) : - IAllureModelObject, - IAllureFlakyProperty, - IAllureKnownProperty, - IAllureMessageProperty, - IAllureMutedProperty, - IAllureTraceProperty + IAllureStatusDetails { public static string? Validate(JsonElement json) => default; diff --git a/tests/Allure.Testing/Assertions/Model/AllureTestResult.cs b/tests/Allure.Testing/Assertions/Model/AllureTestResult.cs index 19f16ff0..2555d630 100644 --- a/tests/Allure.Testing/Assertions/Model/AllureTestResult.cs +++ b/tests/Allure.Testing/Assertions/Model/AllureTestResult.cs @@ -1,4 +1,3 @@ -using System; using System.Text.Json; using Allure.Testing.Assertions.Model.Properties; diff --git a/tests/Allure.Testing/Assertions/Model/IAllureAttachment.cs b/tests/Allure.Testing/Assertions/Model/IAllureAttachment.cs new file mode 100644 index 00000000..9bafb235 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/IAllureAttachment.cs @@ -0,0 +1,11 @@ +using Allure.Testing.Assertions.Model.Properties; + +namespace Allure.Testing.Assertions.Model; + +public interface IAllureAttachment : + IAllureModelObject, + IAllureNameProperty, + IAllureAttachmentSourceProperty, + IAllureLinkTypeProperty + + where TSelf : IAllureAttachment, IAllureModelObject; diff --git a/tests/Allure.Testing/Assertions/Model/IAllureStatusDetails.cs b/tests/Allure.Testing/Assertions/Model/IAllureStatusDetails.cs new file mode 100644 index 00000000..4fc21310 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/IAllureStatusDetails.cs @@ -0,0 +1,13 @@ +using Allure.Testing.Assertions.Model.Properties; + +namespace Allure.Testing.Assertions.Model; + +public interface IAllureStatusDetails : + IAllureModelObject, + IAllureFlakyProperty, + IAllureKnownProperty, + IAllureMessageProperty, + IAllureMutedProperty, + IAllureTraceProperty + + where TSelf : IAllureStatusDetails, IAllureModelObject; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAftersProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAftersProperty.cs index 303ff5ea..d25a7340 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAftersProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAftersProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(ItemMethodName = "AfterFixture")] public interface IAllureAftersProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureObjectArrayProperty -{ -} + where TSelf : IAllureModelObject, IAllureObjectArrayProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentSourceProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentSourceProperty.cs index acdd59b5..30a54d1c 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentSourceProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentSourceProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(PropertyName = "Source")] public interface IAllureAttachmentSourceProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureAttachmentSourceProperty -{ -} + where TSelf : IAllureModelObject, IAllureAttachmentSourceProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentsProperty.cs index b015677c..fca6165a 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentsProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureAttachmentsProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureAttachmentsProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureAttachmentsProperty -{ -} + where TSelf : IAllureModelObject, IAllureAttachmentsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureBeforesProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureBeforesProperty.cs index 6cc37fb4..603756a1 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureBeforesProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureBeforesProperty.cs @@ -2,7 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(ItemMethodName = "BeforeFixture")] public interface IAllureBeforesProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureObjectArrayProperty -{ - -} + where TSelf : IAllureModelObject, IAllureObjectArrayProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureChildrenProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureChildrenProperty.cs index 1b1c1f16..8d67fa1b 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureChildrenProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureChildrenProperty.cs @@ -4,7 +4,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(ItemMethodName = "Child")] public interface IAllureChildrenProperty : IAllureArrayProperty - where TSelf : IAllureModelObject, IAllureChildrenProperty -{ - -} + where TSelf : IAllureModelObject, IAllureChildrenProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionHtmlProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionHtmlProperty.cs index b1e8a450..f0228def 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionHtmlProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionHtmlProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureDescriptionHtmlProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureDescriptionHtmlProperty -{ -} + where TSelf : IAllureModelObject, IAllureDescriptionHtmlProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionProperty.cs index dfa79199..a1ea0430 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureDescriptionProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureDescriptionProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureDescriptionProperty -{ -} + where TSelf : IAllureModelObject, IAllureDescriptionProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureFlakyProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureFlakyProperty.cs index bdabd49c..6e47fb50 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureFlakyProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureFlakyProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureFlakyProperty : IAllureBoolProperty - where TSelf : IAllureModelObject, IAllureFlakyProperty -{ -} + where TSelf : IAllureModelObject, IAllureFlakyProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureFullNameProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureFullNameProperty.cs index d53915a1..da6a0733 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureFullNameProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureFullNameProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureFullNameProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureFullNameProperty -{ -} + where TSelf : IAllureModelObject, IAllureFullNameProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalAttachmentsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalAttachmentsProperty.cs new file mode 100644 index 00000000..41394254 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalAttachmentsProperty.cs @@ -0,0 +1,8 @@ +namespace Allure.Testing.Assertions.Model.Properties; + +[GenerateAllureAssertions( + PropertyName = "Attachments", + MethodName = "GlobalAttachments", + ItemMethodName = "GlobalAttachment")] +public interface IAllureGlobalAttachmentsProperty : IAllureObjectArrayProperty + where TSelf : IAllureModelObject, IAllureGlobalAttachmentsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalErrorsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalErrorsProperty.cs new file mode 100644 index 00000000..81a7f220 --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureGlobalErrorsProperty.cs @@ -0,0 +1,5 @@ +namespace Allure.Testing.Assertions.Model.Properties; + +[GenerateAllureAssertions(PropertyName = "Errors")] +public interface IAllureGlobalErrorsProperty : IAllureObjectArrayProperty + where TSelf : IAllureModelObject, IAllureGlobalErrorsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureHistoryIdProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureHistoryIdProperty.cs index 168abe4f..fa86bade 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureHistoryIdProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureHistoryIdProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureHistoryIdProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureHistoryIdProperty -{ -} + where TSelf : IAllureModelObject, IAllureHistoryIdProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureKnownProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureKnownProperty.cs index 650a910a..0d7d5bd8 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureKnownProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureKnownProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureKnownProperty : IAllureBoolProperty - where TSelf : IAllureModelObject, IAllureKnownProperty -{ -} + where TSelf : IAllureModelObject, IAllureKnownProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLabelsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLabelsProperty.cs index 78463867..024ce01c 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLabelsProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLabelsProperty.cs @@ -1,9 +1,5 @@ -using System.Collections.Immutable; - namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureLabelsProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureLabelsProperty -{ -} + where TSelf : IAllureModelObject, IAllureLabelsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkTypeProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkTypeProperty.cs index 27849455..62cf77fc 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkTypeProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkTypeProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(PropertyName = "Type")] public interface IAllureLinkTypeProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureLinkTypeProperty -{ -} + where TSelf : IAllureModelObject, IAllureLinkTypeProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkUrlProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkUrlProperty.cs index 6263990f..fae06225 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkUrlProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinkUrlProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(PropertyName = "Url")] public interface IAllureLinkUrlProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureLinkUrlProperty -{ -} + where TSelf : IAllureModelObject, IAllureLinkUrlProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinksProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinksProperty.cs index 277a5116..ce7cd1a4 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinksProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureLinksProperty.cs @@ -1,9 +1,5 @@ -using System.Collections.Immutable; - namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureLinksProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureLinksProperty -{ -} + where TSelf : IAllureModelObject, IAllureLinksProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureMessageProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureMessageProperty.cs index d1e62f2a..fa00d804 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureMessageProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureMessageProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureMessageProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureMessageProperty -{ -} + where TSelf : IAllureModelObject, IAllureMessageProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureMutedProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureMutedProperty.cs index ac8f2f58..01037245 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureMutedProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureMutedProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureMutedProperty : IAllureBoolProperty - where TSelf : IAllureModelObject, IAllureMutedProperty -{ -} + where TSelf : IAllureModelObject, IAllureMutedProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureNameProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureNameProperty.cs index 9e4ce294..03e67968 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureNameProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureNameProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureNameProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureNameProperty -{ -} + where TSelf : IAllureModelObject, IAllureNameProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureObjectArrayProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureObjectArrayProperty.cs index 2de4f82a..67345840 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureObjectArrayProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureObjectArrayProperty.cs @@ -2,7 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; public interface IAllureObjectArrayProperty : IAllureArrayProperty, TSelf> where TElement : IAllureModelObject - where TSelf : IAllureModelObject, IAllureObjectArrayProperty -{ - -} + where TSelf : IAllureModelObject, IAllureObjectArrayProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterExcludedProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterExcludedProperty.cs index 322cb2b9..de6a3953 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterExcludedProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterExcludedProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(PropertyName = "Excluded")] public interface IAllureParameterExcludedProperty : IAllureBoolProperty - where TSelf : IAllureModelObject, IAllureParameterExcludedProperty -{ -} + where TSelf : IAllureModelObject, IAllureParameterExcludedProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterValueProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterValueProperty.cs index e315558e..3cc815c5 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterValueProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParameterValueProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions(PropertyName = "Value")] public interface IAllureParameterValueProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureParameterValueProperty -{ -} + where TSelf : IAllureModelObject, IAllureParameterValueProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParametersProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParametersProperty.cs index b26fe646..f49615e8 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureParametersProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureParametersProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureParametersProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureParametersProperty -{ -} + where TSelf : IAllureModelObject, IAllureParametersProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStartProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStartProperty.cs index 2fbd88f5..353ca391 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStartProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStartProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureStartProperty : IAllureLongProperty - where TSelf : IAllureModelObject, IAllureStartProperty -{ -} + where TSelf : IAllureModelObject, IAllureStartProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStatusDetailsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStatusDetailsProperty.cs index f907bf49..5d97e25c 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStatusDetailsProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStatusDetailsProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureStatusDetailsProperty : IAllureObjectProperty - where TSelf : IAllureModelObject, IAllureStatusDetailsProperty -{ -} + where TSelf : IAllureModelObject, IAllureStatusDetailsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStepsProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStepsProperty.cs index cf52837e..da7c3296 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStepsProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStepsProperty.cs @@ -4,6 +4,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureStepsProperty : IAllureObjectArrayProperty - where TSelf : IAllureModelObject, IAllureStepsProperty -{ -} + where TSelf : IAllureModelObject, IAllureStepsProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStopProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStopProperty.cs index 0f938031..99265e30 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureStopProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureStopProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureStopProperty : IAllureLongProperty - where TSelf : IAllureModelObject, IAllureStopProperty -{ -} + where TSelf : IAllureModelObject, IAllureStopProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTestCaseIdProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTestCaseIdProperty.cs index b9d6d0fd..193327c6 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTestCaseIdProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTestCaseIdProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureTestCaseIdProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureTestCaseIdProperty -{ -} + where TSelf : IAllureModelObject, IAllureTestCaseIdProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTimestampProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTimestampProperty.cs new file mode 100644 index 00000000..2c69dd6b --- /dev/null +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTimestampProperty.cs @@ -0,0 +1,5 @@ +namespace Allure.Testing.Assertions.Model.Properties; + +[GenerateAllureAssertions] +public interface IAllureTimestampProperty : IAllureLongProperty + where TSelf : IAllureModelObject, IAllureTimestampProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTitlePathProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTitlePathProperty.cs index 89cee478..5337ea2a 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTitlePathProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTitlePathProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureTitlePathProperty : IAllureArrayProperty - where TSelf : IAllureModelObject, IAllureTitlePathProperty -{ -} + where TSelf : IAllureModelObject, IAllureTitlePathProperty; diff --git a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTraceProperty.cs b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTraceProperty.cs index dcc1b83e..82b665dd 100644 --- a/tests/Allure.Testing/Assertions/Model/Properties/IAllureTraceProperty.cs +++ b/tests/Allure.Testing/Assertions/Model/Properties/IAllureTraceProperty.cs @@ -2,6 +2,4 @@ namespace Allure.Testing.Assertions.Model.Properties; [GenerateAllureAssertions] public interface IAllureTraceProperty : IAllureStringProperty - where TSelf : IAllureModelObject, IAllureTraceProperty -{ -} + where TSelf : IAllureModelObject, IAllureTraceProperty; diff --git a/tests/Allure.Testing/Execution/AllureSampleRunInput.cs b/tests/Allure.Testing/Execution/AllureSampleRunInput.cs index 89356703..5cff1470 100644 --- a/tests/Allure.Testing/Execution/AllureSampleRunInput.cs +++ b/tests/Allure.Testing/Execution/AllureSampleRunInput.cs @@ -44,6 +44,22 @@ public record class AllureSampleRunInput ResolveTestTimeoutSeconds() ); + /// + /// Returns if the input is compatible with the pre-run flow. + /// + /// + /// An input is compatible with the pre-run flow if and only of all of the following are true: + /// + /// The input doesn't define a custom configuration. + /// The input doesn't define environment variables. + /// The input doesn't define extra process arguments. + /// + /// + public bool IsPreRunCompatible => + this.AllureConfiguration is null + && this.EnvironmentVariables.Count == 0 + && this.ProcessArguments.Count == 0; + public static AllureSampleRunInput Default { get; } = new(); static int ResolveTestTimeoutSeconds() diff --git a/tests/Allure.Testing/Internal/NarrowingFunctions.cs b/tests/Allure.Testing/Internal/NarrowingFunctions.cs index 89437eee..2aa0085a 100644 --- a/tests/Allure.Testing/Internal/NarrowingFunctions.cs +++ b/tests/Allure.Testing/Internal/NarrowingFunctions.cs @@ -168,7 +168,7 @@ public static string FormatMismatches(string itemDescription, IEnumerable FormatMismatchLine(itemDescription, e))); - public static (string? expected, string? actual) ExtractExpectedAndActual(string message, int ident) + public static (string? expected, string? actual) ExtractExpectedAndActual(string message, int indent) { var lines = message .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) @@ -177,7 +177,7 @@ public static (string? expected, string? actual) ExtractExpectedAndActual(string .Where(l => l.Length > 0) .ToArray(); - var expectedIdentPrefix = new string(' ', ident * 2); + var expectedIdentPrefix = new string(' ', indent * 2); var expected = lines.TakeWhile(l => !l.StartsWith("but ")).ToList() switch { [] => null, @@ -198,7 +198,7 @@ when IsJsonPropertyDefinedAssertion(first) && second.StartsWith("and ") .TakeWhile(l => !l.StartsWith("at Assert.That(")) .ToList(); - var actualIdentPrefix = new string(' ', ident * 2 + 4); + var actualIdentPrefix = new string(' ', indent * 2 + 4); var actual = actualLines switch { [] => message, diff --git a/tests/Allure.TestingPlatform.Tests/CancelPropertyTests.cs b/tests/Allure.TestingPlatform.Tests/CancelPropertyTests.cs new file mode 100644 index 00000000..c5c8e0cb --- /dev/null +++ b/tests/Allure.TestingPlatform.Tests/CancelPropertyTests.cs @@ -0,0 +1,208 @@ +using Allure.Net.Commons; +using Allure.TestingPlatform.Sdk.Correlation; +using Allure.TestingPlatform.Sdk.Messages; +using Allure.TestingPlatform.Sdk.Properties; +using Allure.TestingPlatform.Tests.Stubs; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.TestHost; + +using AllureTestResult = Allure.Net.Commons.TestResult; + +namespace Allure.TestingPlatform.Tests; + +public class CancelPropertyTests : DataConsumerTestsBase +{ + readonly CorrelationUid correlationUid = new("Bar"); + readonly SessionUid sessionUid = new("Bar"); + + [Test] + public async Task ShouldNotWriteCancelledRunningTest() + { + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new InProgressTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateCancelMessage(), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + + await Assert.That(this.writer.TestResults).IsEmpty(); + } + + [Test] + public async Task ShouldNotWriteCancelledTestWhenCancelArrivesBeforeStart() + { + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateCancelMessage(), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new InProgressTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + + await Assert.That(this.writer.TestResults).IsEmpty(); + } + + [Test] + public async Task ShouldNotWriteCancelledTestCreatedFromSingleTerminalMessage() + { + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateCancelMessage(), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + + await Assert.That(this.writer.TestResults).IsEmpty(); + } + + [Test] + public async Task ShouldIgnoreNonCancellationLabels() + { + var updateTest = new AllureTestUpdateMessage(this.correlationUid, new("test-1")) + { + Properties = + [ + new AllureLabelsProperty( + [ + new(){ name = AllureCancelProperty.CANCEL_LABEL_NAME, value = "false" }, + new(){ name = "not-cancelled", value = "true" }, + ] + ), + ], + }; + + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new InProgressTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + updateTest, + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + + var testResult = await Assert.That(this.writer.TestResults).HasSingleItem(); + await Assert.That(testResult.status).IsEqualTo(Status.passed); + } + + [Test] + public async Task ShouldApplyCancellationTogetherWithOtherPendingUpdates() + { + var updateTest = new AllureTestUpdateMessage(this.correlationUid, new("test-1")) + { + Properties = + [ + new AllureNameProperty("Updated test name"), + new AllureCancelProperty(), + new AllureDescriptionProperty("Updated description"), + ], + }; + + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + updateTest, + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new InProgressTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + + await Assert.That(this.writer.TestResults).IsEmpty(); + } + + [Test] + public async Task ShouldStillWriteContainersForCancelledScopedTest() + { + var startScope = new AllureScopeStartMessage(this.correlationUid, new("scope-1")); + var testsInScope = new AllureScopeTestsMessage( + this.correlationUid, + new("scope-1"), + [new("test-1")] + ); + var startFixture = new AllureBeforeFixtureStartMessage( + this.correlationUid, + new("fixture-1"), + new("scope-1"), + "Scope fixture" + ); + var stopFixture = new AllureFixtureStopMessage(this.correlationUid, new("fixture-1")); + var stopScope = new AllureScopeStopMessage(this.correlationUid, new("scope-1")); + + await this.consumer.ConsumeAsync(DataProducerStub.Instance, startScope, CancellationToken.None); + await this.consumer.ConsumeAsync(DataProducerStub.Instance, testsInScope, CancellationToken.None); + await this.consumer.ConsumeAsync(DataProducerStub.Instance, startFixture, CancellationToken.None); + await this.consumer.ConsumeAsync(DataProducerStub.Instance, stopFixture, CancellationToken.None); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new InProgressTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateCancelMessage(), + CancellationToken.None + ); + await this.consumer.ConsumeAsync( + DataProducerStub.Instance, + this.CreateTestNodeMessage(new PassedTestNodeStateProperty()), + CancellationToken.None + ); + await this.consumer.ConsumeAsync(DataProducerStub.Instance, stopScope, CancellationToken.None); + + var container = await Assert.That(this.writer.TestContainers).HasSingleItem(); + var fixture = await Assert.That(container.befores).HasSingleItem(); + + await Assert.That(this.writer.TestResults).IsEmpty(); + await Assert.That(fixture.name).IsEqualTo("Scope fixture"); + await Assert.That(container.children).HasSingleItem(); + } + + AllureTestUpdateMessage CreateCancelMessage() => new(this.correlationUid, new("test-1")) + { + Properties = [new AllureCancelProperty()], + }; + + TestNodeUpdateMessage CreateTestNodeMessage(IProperty state) => new( + this.sessionUid, + new() + { + Uid = "test-1", + DisplayName = "Test", + Properties = new(state), + } + ); +} diff --git a/tests/Allure.Xunit.v3.Tests/Allure.Xunit.v3.Tests.csproj b/tests/Allure.Xunit.v3.Tests/Allure.Xunit.v3.Tests.csproj new file mode 100644 index 00000000..d83f0a85 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Allure.Xunit.v3.Tests.csproj @@ -0,0 +1,43 @@ + + + + enable + Exe + true + MicrosoftTestingPlatform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Allure.Xunit.v3.Tests/AllureIds/AllureIdTests.cs b/tests/Allure.Xunit.v3.Tests/AllureIds/AllureIdTests.cs new file mode 100644 index 00000000..04b12d07 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/AllureIds/AllureIdTests.cs @@ -0,0 +1,16 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.AllureIds; + +class AllureIdTests +{ + [Test] + public async Task CheckAllureIdAttributeWorks(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.AllureIdAttributeOnMethod, token); + + await Assert.That(results).HasSingleTestResult() + .That.HasSingleLabel("ALLURE_ID") + .With.Value("1001"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/AllureIds/Samples/AllureIdAttributeOnMethod.cs b/tests/Allure.Xunit.v3.Tests/AllureIds/Samples/AllureIdAttributeOnMethod.cs new file mode 100644 index 00000000..d22d1186 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/AllureIds/Samples/AllureIdAttributeOnMethod.cs @@ -0,0 +1,12 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.AllureIds.AllureIdAttributeOnMethod +{ + public class TestsClass + { + [Fact] + [AllureId(1001)] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/BddLabelTests.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/BddLabelTests.cs new file mode 100644 index 00000000..15d5eb08 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/BddLabelTests.cs @@ -0,0 +1,64 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.BddLabels; + +class BddLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.BddHierarchyAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckBddHierarchyOnTestMethodWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes.OnTestMethod.TestMethod" + ); + await Assert.That(testResult).HasSingleLabel("epic").With.Value("Foo"); + await Assert.That(testResult).HasSingleLabel("feature").With.Value("Bar"); + await Assert.That(testResult).HasSingleLabel("story").With.Value("Baz"); + } + + [Test] + public async Task CheckBddHierarchyOnTestClassWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes.OnTestClass.TestMethod" + ); + await Assert.That(testResult).HasSingleLabel("epic").With.Value("Foo"); + await Assert.That(testResult).HasSingleLabel("feature").With.Value("Bar"); + await Assert.That(testResult).HasSingleLabel("story").With.Value("Baz"); + } + + [Test] + public async Task CheckBddHierarchyOnBaseClassWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes.OnBaseClass.TestMethod" + ); + await Assert.That(testResult).HasSingleLabel("epic").With.Value("Foo"); + await Assert.That(testResult).HasSingleLabel("feature").With.Value("Bar"); + await Assert.That(testResult).HasSingleLabel("story").With.Value("Baz"); + } + + [Test] + public async Task CheckBddHierarchyOnInterfaceWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes.OnInterface.TestMethod" + ); + await Assert.That(testResult).HasSingleLabel("epic").With.Value("Foo"); + await Assert.That(testResult).HasSingleLabel("feature").With.Value("Bar"); + await Assert.That(testResult).HasSingleLabel("story").With.Value("Baz"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/EpicLabelTests.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/EpicLabelTests.cs new file mode 100644 index 00000000..1af9a0af --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/EpicLabelTests.cs @@ -0,0 +1,52 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.BddLabels; + +class EpicLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.EpicAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckEpicOnTestMethodWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes.OnTestMethod.TestMethod" + ).With.SingleLabel("epic").That.HasValue("Foo"); + } + + [Test] + public async Task CheckEpicOnTestClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes.OnTestClass.TestMethod" + ).With.SingleLabel("epic").That.HasValue("Foo"); + } + + [Test] + public async Task CheckEpicOnBaseClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes.OnBaseClass.TestMethod" + ).With.SingleLabel("epic").That.HasValue("Foo"); + } + + [Test] + public async Task CheckEpicOnInterfaceWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes.OnInterface.TestMethod" + ).With.SingleLabel("epic").That.HasValue("Foo"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/FeatureLabelTests.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/FeatureLabelTests.cs new file mode 100644 index 00000000..c5732cce --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/FeatureLabelTests.cs @@ -0,0 +1,52 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.BddLabels; + +class FeatureLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.FeatureAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckFeatureOnTestMethodWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes.OnTestMethod.TestMethod" + ).With.SingleLabel("feature").That.HasValue("Foo"); + } + + [Test] + public async Task CheckFeatureOnTestClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes.OnTestClass.TestMethod" + ).With.SingleLabel("feature").That.HasValue("Foo"); + } + + [Test] + public async Task CheckFeatureOnBaseClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes.OnBaseClass.TestMethod" + ).With.SingleLabel("feature").That.HasValue("Foo"); + } + + [Test] + public async Task CheckFeatureOnInterfaceWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes.OnInterface.TestMethod" + ).With.SingleLabel("feature").That.HasValue("Foo"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnBaseClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnBaseClass.cs new file mode 100644 index 00000000..17036d3b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnBaseClass.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes +{ + [AllureBddHierarchy("Foo", "Bar", "Baz")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnInterface.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnInterface.cs new file mode 100644 index 00000000..be288478 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnInterface.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes +{ + [AllureBddHierarchy("Foo", "Bar", "Baz")] + public class IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestClass.cs new file mode 100644 index 00000000..907b6d7f --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestClass.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes +{ + [AllureBddHierarchy("Foo", "Bar", "Baz")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestMethod.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestMethod.cs new file mode 100644 index 00000000..ddd18231 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/BddHierarchyAttributes/OnTestMethod.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.BddLabels.BddHierarchyAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureBddHierarchy("Foo", "Bar", "Baz")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnBaseClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnBaseClass.cs new file mode 100644 index 00000000..648cf925 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnBaseClass.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes +{ + [AllureEpic("Foo")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnInterface.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnInterface.cs new file mode 100644 index 00000000..8bec7874 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnInterface.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes +{ + [AllureEpic("Foo")] + public class IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestClass.cs new file mode 100644 index 00000000..667aacda --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestClass.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes +{ + [AllureEpic("Foo")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestMethod.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestMethod.cs new file mode 100644 index 00000000..53bc88fb --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/EpicAttributes/OnTestMethod.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.EpicLabels.EpicAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureEpic("Foo")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnBaseClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnBaseClass.cs new file mode 100644 index 00000000..366c6eb5 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnBaseClass.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes +{ + [AllureFeature("Foo")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnInterface.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnInterface.cs new file mode 100644 index 00000000..969cbb52 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnInterface.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes +{ + [AllureFeature("Foo")] + public class IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestClass.cs new file mode 100644 index 00000000..3a9c3aad --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestClass.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes +{ + [AllureFeature("Foo")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestMethod.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestMethod.cs new file mode 100644 index 00000000..e82c0f05 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/FeatureAttributes/OnTestMethod.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.FeatureLabels.FeatureAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureFeature("Foo")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnBaseClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnBaseClass.cs new file mode 100644 index 00000000..b97c1da3 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnBaseClass.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes +{ + [AllureStory("Foo")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnInterface.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnInterface.cs new file mode 100644 index 00000000..a0b7dbf0 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnInterface.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes +{ + [AllureStory("Foo")] + public class IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestClass.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestClass.cs new file mode 100644 index 00000000..a5d197a7 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestClass.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes +{ + [AllureStory("Foo")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestMethod.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestMethod.cs new file mode 100644 index 00000000..6fd18dab --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/Samples/StoryAttributes/OnTestMethod.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureStory("Foo")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/BddLabels/StoryLabelTests.cs b/tests/Allure.Xunit.v3.Tests/BddLabels/StoryLabelTests.cs new file mode 100644 index 00000000..50895e68 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/BddLabels/StoryLabelTests.cs @@ -0,0 +1,52 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.BddLabels; + +class StoryLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.StoryAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckStoryOnTestMethodWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes.OnTestMethod.TestMethod" + ).With.SingleLabel("story").That.HasValue("Foo"); + } + + [Test] + public async Task CheckStoryOnTestClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes.OnTestClass.TestMethod" + ).With.SingleLabel("story").That.HasValue("Foo"); + } + + [Test] + public async Task CheckStoryOnBaseClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes.OnBaseClass.TestMethod" + ).With.SingleLabel("story").That.HasValue("Foo"); + } + + [Test] + public async Task CheckStoryOnInterfaceWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.StoryLabels.StoryAttributes.OnInterface.TestMethod" + ).With.SingleLabel("story").That.HasValue("Foo"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Cli/CliOptionTests.cs b/tests/Allure.Xunit.v3.Tests/Cli/CliOptionTests.cs new file mode 100644 index 00000000..c6da166b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Cli/CliOptionTests.cs @@ -0,0 +1,95 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.Cli; + +class CliOptionTests +{ + const string SampleAssembly = "Allure.Xunit.v3.Tests.Samples.Cli.CliOptions"; + const string SampleType = "Allure.Xunit.v3.Tests.Samples.Cli.CliOptions.TestClass"; + + [Test] + public async Task AllureOffShouldNotWriteResults(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.CliOptions, new() + { + ProcessArguments = ["--", "--allure", "off"], + }, token); + + await Assert.That(results.TestResults).Count().IsEqualTo(0); + await Assert.That(results.Containers).Count().IsEqualTo(0); + await Assert.That(results.Globals).Count().IsEqualTo(0); + } + + [Test] + public async Task AllureOnShouldWriteResults(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.CliOptions, new() + { + ProcessArguments = ["--", "--allure", "on"], + }, token); + + await Assert.That(results.TestResults).Count().IsEqualTo(6); + } + + [Test] + public async Task AllureResultsDirectoryShouldOverrideConfigurationDirectory(CancellationToken token) + { + using var cliDirectory = new TempDirectory("allure-cli-results-"); + + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.CliOptions, new() + { + ProcessArguments = ["--", "--allure-results-directory", cliDirectory.Path], + }, token); + + var cliResults = await AllureSampleRunner.ReadAllureResults(cliDirectory.Directory, token); + + await Assert.That(results.TestResults).Count().IsEqualTo(0); + await Assert.That(results.Containers).Count().IsEqualTo(0); + await Assert.That(results.Globals).Count().IsEqualTo(0); + + await Assert.That(cliResults.TestResults).Count().IsEqualTo(6); + } + + [Test] + public async Task AllureWatchdogOffShouldNotCreateGlobalsOnCrash(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + Lifecycle.AllureSampleRegistry.CrashingProcess, + new() + { + ProcessArguments = ["--", "--allure-watchdog", "off"], + }, + token + ); + + await Assert.That(results.Globals).Count().IsEqualTo(0); + } + + [Test] + public async Task XunitFilterMethodShouldBePreserved(CancellationToken token) + { + var selectedTest = "Allure.Xunit.v3.Tests.Samples.Cli.CliOptions.TestClass.FirstTest"; + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.CliOptions, new() + { + ProcessArguments = ["--", "--filter-method", selectedTest], + }, token); + + await Assert.That(results).HasSingleTestResult(selectedTest); + } + + sealed class TempDirectory(string prefix) : IDisposable + { + public DirectoryInfo Directory { get; } = System.IO.Directory.CreateTempSubdirectory(prefix); + + public string Path => Directory.FullName; + + public void Dispose() + { + Directory.Refresh(); + if (Directory.Exists) + { + Directory.Delete(recursive: true); + } + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Cli/Samples/CliOptions.cs b/tests/Allure.Xunit.v3.Tests/Cli/Samples/CliOptions.cs new file mode 100644 index 00000000..dc18cd5e --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Cli/Samples/CliOptions.cs @@ -0,0 +1,33 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Cli.CliOptions +{ + public class TestClass + { + [Fact] + public void FirstTest() { } + + [Fact] + public void SecondTest() { } + + [Fact] + [AllureId(3001)] + public void AllureIdTest() { } + + [Theory] + [InlineData("foo")] + [InlineData("bar")] + public void ParameterizedTheory(string value) + { + Assert.NotEmpty(value); + } + + [Theory] + [InlineData("baz")] + public void GenericTheory(T value) + { + Assert.NotNull(value); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/GlobalLabelTests.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/GlobalLabelTests.cs new file mode 100644 index 00000000..90c8b9a3 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/GlobalLabelTests.cs @@ -0,0 +1,48 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.CustomLabels; + +class GlobalLabelTests +{ + [Test] + public async Task CheckIfGlobalLabelsConfigurationWorks(CancellationToken token) + { + var results = AllureSampleRunner.RunAsync(AllureSampleRegistry.GlobalLabels, new() + { + AllureConfiguration = new { allure = new { globalLabels = new { foo = "bar", baz = "qux" } } }, + }, token); + + await Assert.That(results).HasTestResults([ + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.CustomLabels.GlobalLabels.TestClass.TestMethod1") + .And.HasLabel((l) => l.HasName("foo").And.HasValue("bar")) + .And.HasLabel((l) => l.HasName("baz").And.HasValue("qux")) + , + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.CustomLabels.GlobalLabels.TestClass.TestMethod2") + .And.HasLabel((l) => l.HasName("foo").And.HasValue("bar")) + .And.HasLabel((l) => l.HasName("baz").And.HasValue("qux")) + , + ]); + } + + [Test] + public async Task CheckIfEnvironmentLabelsWork(CancellationToken token) + { + var results = AllureSampleRunner.RunAsync(AllureSampleRegistry.GlobalLabels, new() + { + EnvironmentVariables = new() + { + ["ALLURE_LABEL_foo"] = "bar", + ["ALLURE_LABEL_baz"] = "qux", + }, + }, token); + + await Assert.That(results).HasTestResults([ + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.CustomLabels.GlobalLabels.TestClass.TestMethod1") + .And.HasLabel((l) => l.HasName("foo").And.HasValue("bar")) + .And.HasLabel((l) => l.HasName("baz").And.HasValue("qux")), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.CustomLabels.GlobalLabels.TestClass.TestMethod2") + .And.HasLabel((l) => l.HasName("foo").And.HasValue("bar")) + .And.HasLabel((l) => l.HasName("baz").And.HasValue("qux")), + ]); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/LabelAttributeTests.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/LabelAttributeTests.cs new file mode 100644 index 00000000..20d65bef --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/LabelAttributeTests.cs @@ -0,0 +1,52 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.CustomLabels; + +class LabelAttributeTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.LabelAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckCustomLabelOnTestMethodWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("foo").With.Value("bar"); + } + + [Test] + public async Task CheckCustomLabelOnTestClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("foo").With.Value("bar"); + } + + [Test] + public async Task CheckCustomLabelOnBaseClassWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("foo").With.Value("bar"); + } + + [Test] + public async Task CheckCustomLabelOnInterfaceWorks() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("foo").With.Value("bar"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/GlobalLabels.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/GlobalLabels.cs new file mode 100644 index 00000000..c25e56c8 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/GlobalLabels.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.CustomLabels.GlobalLabels +{ + public class TestClass + { + [Fact] + public void TestMethod1() { } + + [Fact] + public void TestMethod2() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnBaseClass.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnBaseClass.cs new file mode 100644 index 00000000..2209fd28 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnBaseClass.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes +{ + [AllureLabel("foo", "bar")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnInterface.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnInterface.cs new file mode 100644 index 00000000..fb4b9789 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnInterface.cs @@ -0,0 +1,14 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes +{ + [AllureLabel("foo", "bar")] + public class IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestClass.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestClass.cs new file mode 100644 index 00000000..51c65b03 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestClass.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes +{ + [AllureLabel("foo", "bar")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestMethod.cs b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestMethod.cs new file mode 100644 index 00000000..c9927b8d --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/CustomLabels/Samples/LabelAttributes/OnTestMethod.cs @@ -0,0 +1,12 @@ +using Xunit; +using Allure.Net.Commons.Attributes; + +namespace Allure.Xunit.v3.Tests.Samples.CustomLabels.LabelAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureLabel("foo", "bar")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Descriptions/DescriptionTests.cs b/tests/Allure.Xunit.v3.Tests/Descriptions/DescriptionTests.cs new file mode 100644 index 00000000..7ca85481 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Descriptions/DescriptionTests.cs @@ -0,0 +1,29 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.Descriptions; + +class DescriptionTests +{ + [Test] + public async Task CheckDescriptionAttributesWork(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.DescriptionAttributes, token); + + await Assert.That(results).HasSingleTestResult() + .With.Description( + """ + Interface description + + Base class description + + Test class description + + Test method description + """) + .With.DescriptionHtml( + "

Interface HTML

" + + "

Base class HTML

" + + "

Test class HTML

" + + "

Test method HTML

"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Descriptions/Samples/DescriptionAttributes.cs b/tests/Allure.Xunit.v3.Tests/Descriptions/Samples/DescriptionAttributes.cs new file mode 100644 index 00000000..ad5ad924 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Descriptions/Samples/DescriptionAttributes.cs @@ -0,0 +1,23 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Descriptions.DescriptionAttributes +{ + [AllureDescription("Interface description", Append = true)] + [AllureDescriptionHtml("

Interface HTML

", Append = true)] + public interface IInterface { } + + [AllureDescription("Base class description", Append = true)] + [AllureDescriptionHtml("

Base class HTML

", Append = true)] + public class BaseClass { } + + [AllureDescription("Test class description", Append = true)] + [AllureDescriptionHtml("

Test class HTML

", Append = true)] + public class TestClass : BaseClass, IInterface + { + [Fact] + [AllureDescription("Test method description", Append = true)] + [AllureDescriptionHtml("

Test method HTML

", Append = true)] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/GeneratorTests.cs b/tests/Allure.Xunit.v3.Tests/Generator/GeneratorTests.cs new file mode 100644 index 00000000..023dc8e6 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/GeneratorTests.cs @@ -0,0 +1,104 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.Generator; + +class GeneratorTests +{ + [Test] + public async Task ShouldReportRawResultsWhenAllureAttributeNotGenerated(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.ApplyAttributeDisabled, + token + ); + + var testResult = await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Generator.ApplyAttributeDisabled.TestClass.TestMethod(argument: \"value-1\")" + ); + + await Assert.That(testResult).HasNoLabel("tag") + .And.HasParametersMatching([]); + } + + [Test] + public async Task ManualAssemblyAttributeShouldSuppressGeneratedDuplicateAndReportResults(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.ManualAssemblyAttribute, + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(2); + } + + [Test] + public async Task SelfRegistrationDisabledShouldNotReportResultsByDefault(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.SelfRegistrationDisabled, + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(0); + await Assert.That(results.Containers).Count().IsEqualTo(0); + await Assert.That(results.Globals).Count().IsEqualTo(0); + } + + [Test] + public async Task SelfRegistrationDisabledShouldAllowCustomRunnerReporter(CancellationToken token) + { + string markerPath = null; + try + { + markerPath = Path.GetTempFileName(); + + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.CustomRunnerReporter, + new() + { + EnvironmentVariables = + { + ["__ALLURE_MARKER_FILE__"] = markerPath, + }, + }, + token + ); + + await Assert.That(new FileInfo(markerPath)).Exists(); + await Assert.That(File.ReadAllTextAsync(markerPath, token)).IsEqualTo("custom reporter works"); + } + finally + { + if (markerPath is not null) + { + File.Delete(markerPath); + } + } + } + + [Test] + public async Task CustomEntryPointWithCustomAllureConfiguration(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.CustomStartupObject, + token + ); + + await Assert.That(results).HasSingleTestResult() + .With.SingleLabel("startup-object") + .That.HasValue("custom"); + } + + [Test] + public async Task ShouldSupportCustomEntryPointCallingRunner(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.RunnerHelper, + token + ); + + await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Generator.RunnerHelper.TestClass.TestMethod" + ); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/ApplyAttributeDisabled.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/ApplyAttributeDisabled.cs new file mode 100644 index 00000000..2c9cbbfa --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/ApplyAttributeDisabled.cs @@ -0,0 +1,16 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Generator.ApplyAttributeDisabled +{ + public class TestClass + { + [Theory] + [InlineData("value-1")] + [AllureTag("tag-from-attribute")] + public void TestMethod(string argument) + { + _ = argument; + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomRunnerReporter.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomRunnerReporter.cs new file mode 100644 index 00000000..9c2c0d41 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomRunnerReporter.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit; +using Xunit.Runner.Common; +using Xunit.Sdk; +using Xunit.v3; + +[assembly: RegisterRunnerReporter(typeof(Allure.Xunit.v3.Tests.Samples.Generator.CustomRunnerReporter.CustomRunnerReporter))] + +namespace Allure.Xunit.v3.Tests.Samples.Generator.CustomRunnerReporter +{ + public sealed class CustomRunnerReporter : IRunnerReporter + { + public bool CanBeEnvironmentallyEnabled => true; + + public string Description => "Custom runner reporter for generator tests"; + + public bool ForceNoLogo => false; + + public bool IsEnvironmentallyEnabled => true; + + public string RunnerSwitch => "custom-generator-test"; + + public ValueTask CreateMessageHandler( + IRunnerLogger logger, + IMessageSink diagnosticMessageSink + ) + { + var markerPath = Environment.GetEnvironmentVariable("__ALLURE_MARKER_FILE__"); + if (!string.IsNullOrEmpty(markerPath) && File.Exists(markerPath)) + { + File.WriteAllText(markerPath, "custom reporter works"); + } + + return new(new DefaultRunnerReporterMessageHandler(logger)); + } + } + + public class TestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomStartupObject.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomStartupObject.cs new file mode 100644 index 00000000..8ff6c323 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/CustomStartupObject.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Allure.TestingPlatform.Sdk.Registration; +using Xunit; +using Allure.Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Generator.CustomStartupObject +{ + public class Program + { + public static async Task Main(string[] args) => + await Allure.Xunit.AllureXunitEntryPoint.RunAsync(allure => + { + allure.UseConfiguration(serviceProvider => + { + var configuration = AllureRegistrationDefaults.ReadAllureConfiguration(serviceProvider); + configuration.GlobalLabels["startup-object"] = "custom"; + return configuration; + }); + }, args); + } + + public class TestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/ManualAssemblyAttribute.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/ManualAssemblyAttribute.cs new file mode 100644 index 00000000..4be92e6b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/ManualAssemblyAttribute.cs @@ -0,0 +1,16 @@ +using Allure.Xunit; +using Xunit; + +[assembly: AllureXunit] + +namespace Allure.Xunit.v3.Tests.Samples.Generator.ManualAssemblyAttribute +{ + public class TestClass + { + [Fact] + public void FirstTest() { } + + [Fact] + public void SecondTest() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/RunnerHelper.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/RunnerHelper.cs new file mode 100644 index 00000000..725f4c73 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/RunnerHelper.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Generator.RunnerHelper +{ + public class Program + { + public static async Task Main(string[] args) => + await Allure.Xunit.AllureXunitRunner.RunAsync( + SelfRegisteredExtensions.AddSelfRegisteredExtensions, + args + ); + } + + public class TestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Generator/Samples/SelfRegistrationDisabled.cs b/tests/Allure.Xunit.v3.Tests/Generator/Samples/SelfRegistrationDisabled.cs new file mode 100644 index 00000000..8ebef772 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Generator/Samples/SelfRegistrationDisabled.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Generator.SelfRegistrationDisabled +{ + public class TestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/GlobalSetup.cs b/tests/Allure.Xunit.v3.Tests/GlobalSetup.cs new file mode 100644 index 00000000..5196d71d --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/GlobalSetup.cs @@ -0,0 +1,13 @@ +[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + +#if ALLURE_TEST_PARALLEL + +[assembly: ParallelLimiter] + +#else + +[assembly: NotInParallel(["Allure.Xunit.v3", "Allure.TestingPlatform", "Allure.Net.Commons"])] + +#endif + +namespace Allure.Xunit.v3.Tests; diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/AsyncTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/AsyncTests.cs new file mode 100644 index 00000000..4a8351ec --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/AsyncTests.cs @@ -0,0 +1,37 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class AsyncTests +{ + readonly static AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.AsyncTests, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(6); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task ShouldReportAllAsyncTests() + { + await Assert.That(results.Value).HasTestResults([ + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass1.AsyncFact1"), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass1.AsyncTheory1(value: \"foo\")") + .With.SingleParameter("value").That.HasValue("\"foo\""), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass1.AsyncTheory1(value: \"bar\")") + .With.SingleParameter("value").That.HasValue("\"bar\""), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass2.AsyncFact2"), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass2.AsyncTheory2(value: \"foo\")") + .With.SingleParameter("value").That.HasValue("\"foo\""), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests.AsyncTestClass2.AsyncTheory2(value: \"bar\")") + .With.SingleParameter("value").That.HasValue("\"bar\""), + ]); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/FailExceptionTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/FailExceptionTests.cs new file mode 100644 index 00000000..cfb8da35 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/FailExceptionTests.cs @@ -0,0 +1,29 @@ +using System.Collections.Immutable; +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class FailExceptionTests +{ + [Test] + public async Task CheckPassingFactIsRecordedAsPassed(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.SingleBroken, + new() + { + AllureConfiguration = new + { + allure = new + { + failExceptions = ImmutableArray.Create("System.InvalidOperationException"), + } + }, + }, + token + ); + + await Assert.That(results).HasSingleTestResult().With.Status(AllureStatus.Failed); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/FailureTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/FailureTests.cs new file mode 100644 index 00000000..12adb3cc --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/FailureTests.cs @@ -0,0 +1,40 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class FailureTests +{ + readonly static AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.Failures, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(2); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task ShouldReportClassConstructorException() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Failures.BadClass.TestMethod" + ) + .With.Status(AllureStatus.Broken) + .With.StatusDetails((sd) => sd.HasMessage((m) => m.Contains("Constructor exploded."))); + } + + [Test] + public async Task ShouldReportTheoryDataException() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Failures.GoodClass.BadTheory" + ) + .With.Status(AllureStatus.Broken) + .With.StatusDetails((sd) => sd.HasMessage((m) => m.Contains("Data source exploded."))); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/OutcomeTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/OutcomeTests.cs new file mode 100644 index 00000000..e6828b00 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/OutcomeTests.cs @@ -0,0 +1,72 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class OutcomeTests +{ + readonly static AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.Outcomes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckPassingFactIsRecordedAsPassed() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Outcomes.TestClass.PassingFact" + ) + .With.Status(AllureStatus.Passed); + } + + [Test] + public async Task CheckAssertionFailureIsRecordedAsFailed() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Outcomes.TestClass.FailingFact" + ) + .With.Status(AllureStatus.Failed) + .With.StatusDetails( + (sd) => sd + .HasMessage((m) => m.Contains("Expected:").And.Contains("Actual:")) + .And.HasTrace((t) => t.Contains("Xunit.MicrosoftTestingPlatform.XunitException")) + ); + } + + [Test] + public async Task CheckUnexpectedExceptionIsRecordedAsBroken() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Outcomes.TestClass.BrokenFact" + ) + .With.Status(AllureStatus.Broken) + .With.StatusDetails( + (sd) => sd + .HasMessage((m) => m.Contains("Something went wrong.")) + .And.HasTrace((t) => t.Contains("Xunit.MicrosoftTestingPlatform.XunitException") + .And.Contains("InvalidOperationException")) + ); + } + + [Test] + public async Task CheckSkippedFactIsRecordedAsSkipped() + { + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Outcomes.TestClass.SkippedFact" + ) + .With.Status(AllureStatus.Skipped) + .With.StatusDetails( + (sd) => sd.HasMessage( + (m) => m.Contains("Not part of this run.") + ) + ); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/AsyncTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/AsyncTests.cs new file mode 100644 index 00000000..3d99d00d --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/AsyncTests.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.AsyncTests +{ + public class AsyncTestClass1 + { + [Fact] + public async Task AsyncFact1() + { + await Task.Yield(); + } + + [Theory] + [InlineData("foo")] + [InlineData("bar")] + public async Task AsyncTheory1(string value) + { + await Task.Yield(); + Assert.NotEmpty(value); + } + } + + public class AsyncTestClass2 + { + [Fact] + public async Task AsyncFact2() + { + await Task.Yield(); + } + + [Theory] + [InlineData("foo")] + [InlineData("bar")] + public async Task AsyncTheory2(string value) + { + await Task.Yield(); + Assert.NotEmpty(value); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/CrashingProcess.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/CrashingProcess.cs new file mode 100644 index 00000000..389023ca --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/CrashingProcess.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.CrashingProcess +{ + public class TestClass + { + [Fact] + public void TestMethod() + { + Environment.FailFast("Crash from sample test."); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Failures.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Failures.cs new file mode 100644 index 00000000..0629612a --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Failures.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.Failures +{ + public class BadClass + { + public BadClass() + { + throw new InvalidOperationException("Constructor exploded."); + } + + [Fact] + public void TestMethod() { } + } + + public class GoodClass + { + public static IEnumerable ThrowingData() => + throw new InvalidOperationException("Data source exploded."); + + [Theory] + [MemberData(nameof(ThrowingData))] + public void BadTheory(string value) + { + _ = value; + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Outcomes.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Outcomes.cs new file mode 100644 index 00000000..fc7bfc67 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Outcomes.cs @@ -0,0 +1,26 @@ +using System; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.Outcomes +{ + public class TestClass + { + [Fact] + public void PassingFact() { } + + [Fact] + public void FailingFact() + { + Assert.Equal(1, 2); + } + + [Fact] + public void BrokenFact() + { + throw new InvalidOperationException("Something went wrong."); + } + + [Fact(Skip = "Not part of this run.")] + public void SkippedFact() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/SingleBroken.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/SingleBroken.cs new file mode 100644 index 00000000..51bee95b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/SingleBroken.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.SingleBroken +{ + public class TestClass + { + [Fact] + public void BrokenFact() + { + throw new InvalidOperationException("Something went wrong."); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Theories.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Theories.cs new file mode 100644 index 00000000..fc14304a --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/Samples/Theories.cs @@ -0,0 +1,17 @@ +using System; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories +{ + public class TestClass + { + [Theory] + [InlineData("first")] + [InlineData("second")] + [InlineData("third")] + public void TestMethod(string value) + { + Assert.NotEmpty(value); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/TheoryTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/TheoryTests.cs new file mode 100644 index 00000000..84c10224 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/TheoryTests.cs @@ -0,0 +1,87 @@ +using System.Collections.Immutable; +using Allure.Testing; +using Allure.Testing.Assertions.Model; +using TUnit.Assertions.Enums; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class TheoryTests +{ + readonly static AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.Theories, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(3); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckResultWithParametersRecordedForEachRow() + { + var expectedFullName = "Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories:" + + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories." + + "TestClass.TestMethod(System.String)"; + ImmutableArray expectedTitlePath = [ + "Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories", + "Allure", + "Xunit", + "v3", + "Tests", + "Samples", + "Lifecycle", + "Theories", + "TestClass", + "TestMethod(System.String)", + ]; + + await Assert.That(results.Value).HasTestResults([ + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories.TestClass.TestMethod(value: \"first\")") + .And.HasFullName(expectedFullName) + .And.HasTitlePath((p) => p.IsEquivalentTo(expectedTitlePath, CollectionOrdering.Matching)) + .And.HasParametersMatching([ + (p) => p.HasName("value").And.HasValue("\"first\""), + ]), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories.TestClass.TestMethod(value: \"second\")") + .And.HasFullName(expectedFullName) + .And.HasTitlePath((p) => p.IsEquivalentTo(expectedTitlePath, CollectionOrdering.Matching)) + .And.HasParametersMatching([ + (p) => p.HasName("value").And.HasValue("\"second\""), + ]), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.Lifecycle.Theories.TestClass.TestMethod(value: \"third\")") + .And.HasFullName(expectedFullName) + .And.HasTitlePath((p) => p.IsEquivalentTo(expectedTitlePath, CollectionOrdering.Matching)) + .And.HasParametersMatching([ + (p) => p.HasName("value").And.HasValue("\"third\""), + ]), + ]); + } + + [Test] + public async Task CheckTheoryRowsShareTestCaseId() + { + var testCaseId = await Assert.That(results.Value).HasTestResultAt(0).With.TestCaseId(); + + await Assert.That(results.Value).HasTestResults([ + (tr) => tr.HasTestCaseId(testCaseId), + (tr) => tr.HasTestCaseId(testCaseId), + (tr) => tr.HasTestCaseId(testCaseId), + ]); + } + + [Test] + public async Task CheckTheoryRowsHaveDifferentHistoryIds() + { + ImmutableHashSet values = [ + await Assert.That(results.Value).HasTestResultAt(0).With.HistoryId(), + await Assert.That(results.Value).HasTestResultAt(1).With.HistoryId(), + await Assert.That(results.Value).HasTestResultAt(2).With.HistoryId(), + ]; + + await Assert.That(values).Count().IsEqualTo(3); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Lifecycle/WatchdogTests.cs b/tests/Allure.Xunit.v3.Tests/Lifecycle/WatchdogTests.cs new file mode 100644 index 00000000..9bc6f82a --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Lifecycle/WatchdogTests.cs @@ -0,0 +1,24 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.Lifecycle; + +class WatchdogTests +{ + [Test] + public async Task ShouldRecordGlobalErrorWhenTestProcessCrashes(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync( + AllureSampleRegistry.CrashingProcess, + token + ); + + await Assert.That(results) + .HasSingleGlobals() + .With.SingleError((error) => error.HasMessage((message) => + message + .Contains("Test host application process") + .And.Contains("(PID=") + .And.Contains("has crashed") + .And.Contains("Exit code:"))); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Links/LinkTests.cs b/tests/Allure.Xunit.v3.Tests/Links/LinkTests.cs new file mode 100644 index 00000000..3a331dd9 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Links/LinkTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.Links; + +class LinkTests +{ + [Test] + public async Task CheckLinkAttributesWork(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.LinkAttributes, new() + { + AllureConfiguration = new + { + allure = new + { + links = new[] + { + "https://issues.example.org/{issue}", + "https://tms.example.org/{tms}", + "https://custom.example.org/{custom}", + }, + }, + }, + }, token); + + await Assert.That(results).HasSingleTestResult() + .With.Links().Count().IsEqualTo(12); + + var testResult = await Assert.That(results).HasSingleTestResult(); + await Assert.That(testResult) + .HasLink(link => link.HasUrl("url-1").And.HasNoName().And.HasNoType()) + .And.HasLink(link => link.HasUrl("https://issues.example.org/ISSUE-2").And.HasName("Issue 2").And.HasType("issue")) + .And.HasLink(link => link.HasUrl("https://tms.example.org/TMS-3").And.HasName("TMS 3").And.HasType("tms")) + .And.HasLink(link => link.HasUrl("url-4").And.HasName("Link 4").And.HasNoType()) + .And.HasLink(link => link.HasUrl("https://issues.example.org/ISSUE-5").And.HasName("Issue 5").And.HasType("issue")) + .And.HasLink(link => link.HasUrl("https://tms.example.org/TMS-6").And.HasName("TMS 6").And.HasType("tms")) + .And.HasLink(link => link.HasUrl("https://custom.example.org/url-7").And.HasNoName().And.HasType("custom")) + .And.HasLink(link => link.HasUrl("https://issues.example.org/ISSUE-8").And.HasNoName().And.HasType("issue")) + .And.HasLink(link => link.HasUrl("https://tms.example.org/TMS-9").And.HasNoName().And.HasType("tms")) + .And.HasLink(link => link.HasUrl("https://custom.example.org/url-10").And.HasName("Link 10").And.HasType("custom")) + .And.HasLink(link => link.HasUrl("https://issues.example.org/ISSUE-11").And.HasNoName().And.HasType("issue")) + .And.HasLink(link => link.HasUrl("https://tms.example.org/TMS-12").And.HasNoName().And.HasType("tms")); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Links/Samples/LinkAttributes.cs b/tests/Allure.Xunit.v3.Tests/Links/Samples/LinkAttributes.cs new file mode 100644 index 00000000..a657f627 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Links/Samples/LinkAttributes.cs @@ -0,0 +1,27 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Links.LinkAttributes +{ + [AllureLink("url-1")] + [AllureIssue("ISSUE-2", Title = "Issue 2")] + [AllureTmsItem("TMS-3", Title = "TMS 3")] + public interface IInterface { } + + [AllureLink("url-4", Title = "Link 4")] + [AllureIssue("ISSUE-5", Title = "Issue 5")] + [AllureTmsItem("TMS-6", Title = "TMS 6")] + public class BaseClass { } + + [AllureLink("url-7", Type = "custom")] + [AllureIssue("ISSUE-8")] + [AllureTmsItem("TMS-9")] + public class TestClass : BaseClass, IInterface + { + [Fact] + [AllureLink("url-10", Title = "Link 10", Type = "custom")] + [AllureIssue("ISSUE-11")] + [AllureTmsItem("TMS-12")] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/MetaAttributes/MetaAttributeTests.cs b/tests/Allure.Xunit.v3.Tests/MetaAttributes/MetaAttributeTests.cs new file mode 100644 index 00000000..d9f9c9f2 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/MetaAttributes/MetaAttributeTests.cs @@ -0,0 +1,23 @@ +using Allure.Testing; + +namespace Allure.Xunit.v3.Tests.MetaAttributes; + +class MetaAttributeTests +{ + [Test] + public async Task CheckMetaAttributeWorks(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.MetaAttributes, token); + + await Assert.That(results).HasSingleTestResult() + .With.OnlyOneLabel(l => l.HasName("epic").And.HasValue("Foo")) + .With.OnlyOneLabel(l => l.HasName("owner").And.HasValue("John Doe")) + .With.OnlyOneLabel(l => l.HasName("feature").And.HasValue("Bar")) + .With.OnlyOneLabel(l => l.HasName("tag").And.HasValue("foo")) + .With.OnlyOneLabel(l => l.HasName("tag").And.HasValue("bar")) + .With.OnlyOneLabel(l => l.HasName("story").And.HasValue("Baz")) + .With.OnlyOneLabel(l => l.HasName("severity").And.HasValue("critical")) + .With.OnlyOneLabel(l => l.HasName("suite").And.HasValue("Qux")) + .With.OnlyOneLink(link => link.HasUrl("https://foo.bar/").And.HasNoName().And.HasNoType()); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/MetaAttributes/Samples/MetaAttributes.cs b/tests/Allure.Xunit.v3.Tests/MetaAttributes/Samples/MetaAttributes.cs new file mode 100644 index 00000000..f6ea1f32 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/MetaAttributes/Samples/MetaAttributes.cs @@ -0,0 +1,23 @@ +using Allure.Net.Commons; +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.MetaAttributes.MetaAttributes +{ + [AllureEpic("Foo")] + [AllureOwner("John Doe")] + [AllureFeature("Bar")] + [AllureTag("foo", "bar")] + [AllureStory("Baz")] + [AllureSeverity(SeverityLevel.critical)] + [AllureSuite("Qux")] + [AllureLink("https://foo.bar/")] + public class CustomAllureAttribute : AllureMetaAttribute { } + + public class TestClass + { + [Fact] + [CustomAllure] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Metadata/Samples/XunitMetadata.cs b/tests/Allure.Xunit.v3.Tests/Metadata/Samples/XunitMetadata.cs new file mode 100644 index 00000000..5b74ce7a --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Metadata/Samples/XunitMetadata.cs @@ -0,0 +1,17 @@ +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata +{ + public class DefaultMetadataClass + { + [Fact] + public void PlainFact() { } + + [Theory] + [InlineData("foo")] + public void PlainTheory(string value) + { + Assert.NotEmpty(value); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Metadata/XunitMetadataTests.cs b/tests/Allure.Xunit.v3.Tests/Metadata/XunitMetadataTests.cs new file mode 100644 index 00000000..e502f10c --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Metadata/XunitMetadataTests.cs @@ -0,0 +1,98 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; +using TUnit.Assertions.Enums; + +namespace Allure.Xunit.v3.Tests.Metadata; + +class XunitMetadataTests +{ + readonly static AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.XunitMetadata, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(2); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckFactMethodIdentityMetadataIsMapped() + { + var testResult = await Assert.That(results.Value) + .HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata.DefaultMetadataClass.PlainFact") + .With.FullName( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata:" + + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata." + + "DefaultMetadataClass.PlainFact()") + .With.TitlePath((tp) => tp.IsEquivalentTo( + [ + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata", + "Allure", + "Xunit", + "v3", + "Tests", + "Samples", + "Metadata", + "XunitMetadata", + "DefaultMetadataClass", + ], + CollectionOrdering.Matching)); + await Assert.That(testResult).HasSingleLabel("testClass").With.Value("DefaultMetadataClass"); + await Assert.That(testResult).HasSingleLabel("testMethod").With.Value("PlainFact"); + await Assert.That(testResult).HasSingleLabel("package").With.Value( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata.DefaultMetadataClass" + ); + } + + [Test] + public async Task CheckTheoryMethodIdentityMetadataIsMapped() + { + var testResult = await Assert.That(results.Value) + .HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata.DefaultMetadataClass.PlainTheory(value: \"foo\")") + .With.FullName( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata:" + + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata." + + "DefaultMetadataClass.PlainTheory(System.String)") + .With.TitlePath((tp) => tp.IsEquivalentTo( + [ + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata", + "Allure", + "Xunit", + "v3", + "Tests", + "Samples", + "Metadata", + "XunitMetadata", + "DefaultMetadataClass", + "PlainTheory(System.String)", + ], + CollectionOrdering.Matching)); + await Assert.That(testResult).HasSingleLabel("testClass").With.Value("DefaultMetadataClass"); + await Assert.That(testResult).HasSingleLabel("testMethod").With.Value("PlainTheory"); + await Assert.That(testResult).HasSingleLabel("package").With.Value( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata.DefaultMetadataClass" + ); + } + + [Test] + public async Task CheckDefaultSuiteLabelsAreMappedFromXunitMetadata() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata.DefaultMetadataClass.PlainFact" + ); + + await Assert.That(testResult).HasSingleLabel("parentSuite").With.Value( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata" + ); + await Assert.That(testResult).HasSingleLabel("suite").With.Value( + "Allure.Xunit.v3.Tests.Samples.Metadata.XunitMetadata" + ); + await Assert.That(testResult).HasSingleLabel("subSuite").With.Value("DefaultMetadataClass"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Names/NameTests.cs b/tests/Allure.Xunit.v3.Tests/Names/NameTests.cs new file mode 100644 index 00000000..8b5b0e38 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Names/NameTests.cs @@ -0,0 +1,68 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Names; + +class NameTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.RenamedTestsAndClasses, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(5); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task ShouldRenameFactResultViaAllureName() + { + await Assert.That(results.Value).HasSingleTestResult( + "Lorem Ipsum on FactMethodRenamedInAllure" + ).With.FullName( + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses:" + + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses." + + "TestClass.FactMethodRenamedInAllure()" + ); + } + + [Test] + public async Task ShouldRenameTheoryResultViaAllureName() + { + await Assert.That(results.Value).HasSingleTestResult( + "Lorem Ipsum on TheoryMethodRenamedInAllure" + ).With.FullName( + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses:" + + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses." + + "TestClass.TheoryMethodRenamedInAllure(System.String)" + ); + } + + [Test] + public async Task ShouldRenameFactResultViaXunitDisplayName() + { + await Assert.That(results.Value).HasSingleTestResult( + "Lorem Ipsum on FactMethodRenamedInXunit" + ).With.FullName( + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses:" + + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses." + + "TestClass.FactMethodRenamedInXunit()" + ); + } + + [Test] + public async Task ShouldRenameTheoryResultViaXunitDisplayName() + { + await Assert.That(results.Value).HasSingleTestResult( + "Lorem Ipsum on TheoryMethodRenamedInXunit(value: \"foo\")" + ).With.FullName( + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses:" + + "Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses." + + "TestClass.TheoryMethodRenamedInXunit(System.String)" + ); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Names/Samples/RenamedTestsAndClasses.cs b/tests/Allure.Xunit.v3.Tests/Names/Samples/RenamedTestsAndClasses.cs new file mode 100644 index 00000000..1ae5905a --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Names/Samples/RenamedTestsAndClasses.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Names.RenamedTestsAndClasses +{ + [AllureName("Lorem Ipsum on NamedClass")] + public class NamedClass + { + [Fact] + public void TestMethod() { } + } + + public class TestClass + { + [Fact] + [AllureName("Lorem Ipsum on FactMethodRenamedInAllure")] + public void FactMethodRenamedInAllure() { } + + [Theory] + [InlineData("foo")] + [AllureName("Lorem Ipsum on TheoryMethodRenamedInAllure")] + public void TheoryMethodRenamedInAllure(string value) + { + _ = value; + } + + [Fact(DisplayName = "Lorem Ipsum on FactMethodRenamedInXunit")] + public void FactMethodRenamedInXunit() { } + + [Theory(DisplayName = "Lorem Ipsum on TheoryMethodRenamedInXunit")] + [InlineData("foo")] + public void TheoryMethodRenamedInXunit(string value) + { + _ = value; + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Owners/OwnerLabelTests.cs b/tests/Allure.Xunit.v3.Tests/Owners/OwnerLabelTests.cs new file mode 100644 index 00000000..2d5106d8 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Owners/OwnerLabelTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Owners; + +class OwnerLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.OwnerAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckOwnerOnTestMethodWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Owners.OwnerAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("owner").With.Value("John Doe"); + + [Test] + public async Task CheckOwnerOnTestClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Owners.OwnerAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("owner").With.Value("John Doe"); + + [Test] + public async Task CheckOwnerOnBaseClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Owners.OwnerAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("owner").With.Value("John Doe"); + + [Test] + public async Task CheckOwnerOnInterfaceWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Owners.OwnerAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("owner").With.Value("John Doe"); +} diff --git a/tests/Allure.Xunit.v3.Tests/Owners/Samples/OwnerAttributes.cs b/tests/Allure.Xunit.v3.Tests/Owners/Samples/OwnerAttributes.cs new file mode 100644 index 00000000..0578a79b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Owners/Samples/OwnerAttributes.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Owners.OwnerAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureOwner("John Doe")] + public void TestMethod() { } + } + + [AllureOwner("John Doe")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureOwner("John Doe")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureOwner("John Doe")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Parameters/ParameterTests.cs b/tests/Allure.Xunit.v3.Tests/Parameters/ParameterTests.cs new file mode 100644 index 00000000..7ad3b745 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Parameters/ParameterTests.cs @@ -0,0 +1,37 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Parameters; + +class ParameterTests +{ + [Test] + public async Task CheckParameterAttributesOnTheoryParametersWork(CancellationToken token) + { + var results = await AllureSampleRunner.RunAsync(AllureSampleRegistry.ParameterAttributesOnTheoryParameters, token); + + await Assert.That(results).HasSingleTestResult() + .With.ParametersMatching([ + p => p.HasName("name1") + .And.HasValue("\"value-1\"") + .And.HasNoMode() + .And.HasExcluded(false), + p => p.HasName("name2") + .And.HasValue("\"value-2\"") + .And.HasMode(AllureParameterMode.Masked) + .And.HasExcluded(false), + p => p.HasName("name3") + .And.HasValue("\"value-3\"") + .And.HasMode(AllureParameterMode.Hidden) + .And.HasExcluded(false), + p => p.HasName("name4") + .And.HasValue("\"value-4\"") + .And.HasNoMode() + .And.HasExcluded(true), + p => p.HasName("name5") + .And.HasValue("\"value-5\"") + .And.HasMode(AllureParameterMode.Masked) + .And.HasExcluded(true), + ]); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Parameters/Samples/ParameterAttributesOnTheoryParameters.cs b/tests/Allure.Xunit.v3.Tests/Parameters/Samples/ParameterAttributesOnTheoryParameters.cs new file mode 100644 index 00000000..178d57f7 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Parameters/Samples/ParameterAttributesOnTheoryParameters.cs @@ -0,0 +1,25 @@ +using Allure.Net.Commons; +using Allure.Net.Commons.Attributes; +using Xunit; + +#pragma warning disable xUnit1026 + +namespace Allure.Xunit.v3.Tests.Samples.Parameters.ParameterAttributesOnTheoryParameters +{ + public class TestClass + { + [Theory] + [InlineData("value-1", "ignored", "value-2", "value-3", "value-4", "value-5")] + public void TestMethod( + string name1, + [AllureParameter(Ignore = true)] string ignored, + [AllureParameter(Name = "name2", Mode = ParameterMode.Masked)] string renamed1, + [AllureParameter(Mode = ParameterMode.Hidden)] string name3, + [AllureParameter(Excluded = true)] string name4, + [AllureParameter(Name = "name5", Mode = ParameterMode.Masked, Excluded = true)] string renamed2 + ) + { + _ = name1 = ignored = renamed1 = name3 = name4 = renamed2; + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Severities/Samples/SeverityAttributes.cs b/tests/Allure.Xunit.v3.Tests/Severities/Samples/SeverityAttributes.cs new file mode 100644 index 00000000..a72f17b4 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Severities/Samples/SeverityAttributes.cs @@ -0,0 +1,38 @@ +using Allure.Net.Commons; +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Severities.SeverityAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureSeverity(SeverityLevel.critical)] + public void TestMethod() { } + } + + [AllureSeverity(SeverityLevel.critical)] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSeverity(SeverityLevel.critical)] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSeverity(SeverityLevel.critical)] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Severities/SeverityLabelTests.cs b/tests/Allure.Xunit.v3.Tests/Severities/SeverityLabelTests.cs new file mode 100644 index 00000000..b3698337 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Severities/SeverityLabelTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Severities; + +class SeverityLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.SeverityAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckSeverityOnTestMethodWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Severities.SeverityAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("severity").With.Value("critical"); + + [Test] + public async Task CheckSeverityOnTestClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Severities.SeverityAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("severity").With.Value("critical"); + + [Test] + public async Task CheckSeverityOnBaseClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Severities.SeverityAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("severity").With.Value("critical"); + + [Test] + public async Task CheckSeverityOnInterfaceWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Severities.SeverityAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("severity").With.Value("critical"); +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/ParentSuiteLabelTests.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/ParentSuiteLabelTests.cs new file mode 100644 index 00000000..96e3c4db --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/ParentSuiteLabelTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.SuiteLabels; + +class ParentSuiteLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.ParentSuiteAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckParentSuiteOnTestMethodWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.ParentSuiteAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("parentSuite").With.Value("Parent Suite"); + + [Test] + public async Task CheckParentSuiteOnTestClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.ParentSuiteAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("parentSuite").With.Value("Parent Suite"); + + [Test] + public async Task CheckParentSuiteOnBaseClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.ParentSuiteAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("parentSuite").With.Value("Parent Suite"); + + [Test] + public async Task CheckParentSuiteOnInterfaceWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.ParentSuiteAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("parentSuite").With.Value("Parent Suite"); +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/ParentSuiteAttributes.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/ParentSuiteAttributes.cs new file mode 100644 index 00000000..0a4c3c29 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/ParentSuiteAttributes.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.SuiteLabels.ParentSuiteAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureParentSuite("Parent Suite")] + public void TestMethod() { } + } + + [AllureParentSuite("Parent Suite")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureParentSuite("Parent Suite")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureParentSuite("Parent Suite")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SubSuiteAttributes.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SubSuiteAttributes.cs new file mode 100644 index 00000000..03839341 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SubSuiteAttributes.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.SuiteLabels.SubSuiteAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureSubSuite("Sub Suite")] + public void TestMethod() { } + } + + [AllureSubSuite("Sub Suite")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSubSuite("Sub Suite")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSubSuite("Sub Suite")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteAttributes.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteAttributes.cs new file mode 100644 index 00000000..80046e5d --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteAttributes.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureSuite("Suite")] + public void TestMethod() { } + } + + [AllureSuite("Suite")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSuite("Suite")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSuite("Suite")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteHierarchyAttributes.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteHierarchyAttributes.cs new file mode 100644 index 00000000..9486e8a8 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/Samples/SuiteHierarchyAttributes.cs @@ -0,0 +1,37 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteHierarchyAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureSuiteHierarchy("Parent Suite", "Suite", "Sub Suite")] + public void TestMethod() { } + } + + [AllureSuiteHierarchy("Parent Suite", "Suite", "Sub Suite")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSuiteHierarchy("Parent Suite", "Suite", "Sub Suite")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureSuiteHierarchy("Parent Suite", "Suite", "Sub Suite")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/SubSuiteLabelTests.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SubSuiteLabelTests.cs new file mode 100644 index 00000000..f7fadf08 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SubSuiteLabelTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.SuiteLabels; + +class SubSuiteLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.SubSuiteAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckSubSuiteOnTestMethodWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SubSuiteAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("subSuite").With.Value("Sub Suite"); + + [Test] + public async Task CheckSubSuiteOnTestClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SubSuiteAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("subSuite").With.Value("Sub Suite"); + + [Test] + public async Task CheckSubSuiteOnBaseClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SubSuiteAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("subSuite").With.Value("Sub Suite"); + + [Test] + public async Task CheckSubSuiteOnInterfaceWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SubSuiteAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("subSuite").With.Value("Sub Suite"); +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteHierarchyLabelTests.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteHierarchyLabelTests.cs new file mode 100644 index 00000000..4f3394ee --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteHierarchyLabelTests.cs @@ -0,0 +1,68 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.SuiteLabels; + +class SuiteHierarchyLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.SuiteHierarchyAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckSuiteHierarchyOnTestMethodWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteHierarchyAttributes.OnTestMethod.TestMethod" + ); + + await Assert.That(testResult).HasSingleLabel("parentSuite").With.Value("Parent Suite"); + await Assert.That(testResult).HasSingleLabel("suite").With.Value("Suite"); + await Assert.That(testResult).HasSingleLabel("subSuite").With.Value("Sub Suite"); + } + + [Test] + public async Task CheckSuiteHierarchyOnTestClassWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteHierarchyAttributes.OnTestClass.TestMethod" + ); + + await Assert.That(testResult).HasSingleLabel("parentSuite").With.Value("Parent Suite"); + await Assert.That(testResult).HasSingleLabel("suite").With.Value("Suite"); + await Assert.That(testResult).HasSingleLabel("subSuite").With.Value("Sub Suite"); + } + + [Test] + public async Task CheckSuiteHierarchyOnBaseClassWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteHierarchyAttributes.OnBaseClass.TestMethod" + ); + + await Assert.That(testResult).HasSingleLabel("parentSuite").With.Value("Parent Suite"); + await Assert.That(testResult).HasSingleLabel("suite").With.Value("Suite"); + await Assert.That(testResult).HasSingleLabel("subSuite").With.Value("Sub Suite"); + } + + [Test] + public async Task CheckSuiteHierarchyOnInterfaceWorks() + { + var testResult = await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteHierarchyAttributes.OnInterface.TestMethod" + ); + + await Assert.That(testResult).HasSingleLabel("parentSuite").With.Value("Parent Suite"); + await Assert.That(testResult).HasSingleLabel("suite").With.Value("Suite"); + await Assert.That(testResult).HasSingleLabel("subSuite").With.Value("Sub Suite"); + } +} diff --git a/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteLabelTests.cs b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteLabelTests.cs new file mode 100644 index 00000000..af552ad7 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/SuiteLabels/SuiteLabelTests.cs @@ -0,0 +1,44 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.SuiteLabels; + +class SuiteLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.SuiteAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckSuiteOnTestMethodWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteAttributes.OnTestMethod.TestMethod" + ).That.HasSingleLabel("suite").With.Value("Suite"); + + [Test] + public async Task CheckSuiteOnTestClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteAttributes.OnTestClass.TestMethod" + ).That.HasSingleLabel("suite").With.Value("Suite"); + + [Test] + public async Task CheckSuiteOnBaseClassWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteAttributes.OnBaseClass.TestMethod" + ).That.HasSingleLabel("suite").With.Value("Suite"); + + [Test] + public async Task CheckSuiteOnInterfaceWorks() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.SuiteLabels.SuiteAttributes.OnInterface.TestMethod" + ).That.HasSingleLabel("suite").With.Value("Suite"); +} diff --git a/tests/Allure.Xunit.v3.Tests/Tags/Samples/TagAttributes.cs b/tests/Allure.Xunit.v3.Tests/Tags/Samples/TagAttributes.cs new file mode 100644 index 00000000..d5fed327 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Tags/Samples/TagAttributes.cs @@ -0,0 +1,41 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.Tags.TagAttributes +{ + public class OnTestMethod + { + [Fact] + [AllureTag("foo", "bar")] + [AllureTag("baz")] + public void TestMethod() { } + } + + [AllureTag("foo", "bar")] + [AllureTag("baz")] + public class OnTestClass + { + [Fact] + public void TestMethod() { } + } + + [AllureTag("foo", "bar")] + [AllureTag("baz")] + public class BaseClass { } + + public class OnBaseClass : BaseClass + { + [Fact] + public void TestMethod() { } + } + + [AllureTag("foo", "bar")] + [AllureTag("baz")] + public interface IInterface { } + + public class OnInterface : IInterface + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/Tags/TagLabelTests.cs b/tests/Allure.Xunit.v3.Tests/Tags/TagLabelTests.cs new file mode 100644 index 00000000..9a500731 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/Tags/TagLabelTests.cs @@ -0,0 +1,56 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; + +namespace Allure.Xunit.v3.Tests.Tags; + +class TagLabelTests +{ + static readonly AsyncLocal results = new(); + + [Before(Class)] + public static async Task BeforeAll(ClassHookContext context, CancellationToken token) + { + var output = await AllureSampleRunner.RunAsync(AllureSampleRegistry.TagAttributes, token); + + await Assert.That(output.TestResults).Count().IsEqualTo(4); + + results.Value = output; + context.AddAsyncLocalValues(); + } + + [Test] + public async Task CheckTagsOnTestMethodWork() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Tags.TagAttributes.OnTestMethod.TestMethod" + ) + .That.HasLabel(l => l.HasName("tag").And.HasValue("foo")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("bar")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("baz")); + + [Test] + public async Task CheckTagsOnTestClassWork() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Tags.TagAttributes.OnTestClass.TestMethod" + ) + .That.HasLabel(l => l.HasName("tag").And.HasValue("foo")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("bar")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("baz")); + + [Test] + public async Task CheckTagsOnBaseClassWork() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Tags.TagAttributes.OnBaseClass.TestMethod" + ) + .That.HasLabel(l => l.HasName("tag").And.HasValue("foo")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("bar")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("baz")); + + [Test] + public async Task CheckTagsOnInterfaceWork() => + await Assert.That(results.Value).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.Tags.TagAttributes.OnInterface.TestMethod" + ) + .That.HasLabel(l => l.HasName("tag").And.HasValue("foo")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("bar")) + .And.HasLabel(l => l.HasName("tag").And.HasValue("baz")); +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdPreFilter.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdPreFilter.cs new file mode 100644 index 00000000..c839f851 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdPreFilter.cs @@ -0,0 +1,30 @@ +using Allure.Net.Commons; +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.TestPlans.AllureIdPreFilter +{ + public class SelectedMarkerClass + { + public SelectedMarkerClass() + { + AllureApi.AddGlobalError("selected Allure ID test was constructed"); + } + + [Fact] + [AllureId(3002)] + public void TestMethod() { } + } + + public class UnselectedMarkerClass + { + public UnselectedMarkerClass() + { + AllureApi.AddGlobalError("unselected Allure ID test was constructed"); + } + + [Fact] + [AllureId(3003)] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdTheory.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdTheory.cs new file mode 100644 index 00000000..3d1e6c73 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/AllureIdTheory.cs @@ -0,0 +1,21 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.TestPlans.AllureIdTheory +{ + public class TestClass + { + [Theory] + [InlineData("foo")] + [InlineData("bar")] + [AllureId(3005)] + public void SelectedTheory(string value) + { + Assert.NotEmpty(value); + } + + [Fact] + [AllureId(3999)] + public void UnselectedTest() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/DuplicateAllureIds.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/DuplicateAllureIds.cs new file mode 100644 index 00000000..e51d4cb4 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/DuplicateAllureIds.cs @@ -0,0 +1,20 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.TestPlans.DuplicateAllureIds +{ + public class TestClass + { + [Fact] + [AllureId(3004)] + public void FirstSelectedTest() { } + + [Fact] + [AllureId(3004)] + public void SecondSelectedTest() { } + + [Fact] + [AllureId(3999)] + public void UnselectedTest() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/RuntimeGuard.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/RuntimeGuard.cs new file mode 100644 index 00000000..75be940b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/RuntimeGuard.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.TestPlans.RuntimeGuard +{ + public class TestClass + { + [Fact] + public void TestMethod() { } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/TestPlan.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/TestPlan.cs new file mode 100644 index 00000000..f6d6c5f5 --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/Samples/TestPlan.cs @@ -0,0 +1,33 @@ +using Allure.Net.Commons.Attributes; +using Xunit; + +namespace Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan +{ + public class TestClass + { + [Fact] + public void FirstTest() { } + + [Fact] + public void SecondTest() { } + + [Fact] + [AllureId(3001)] + public void AllureIdTest() { } + + [Theory] + [InlineData("foo")] + [InlineData("bar")] + public void ParameterizedTheory(string value) + { + Assert.NotEmpty(value); + } + + [Theory] + [InlineData("baz")] + public void GenericTheory(T value) + { + Assert.NotNull(value); + } + } +} diff --git a/tests/Allure.Xunit.v3.Tests/TestPlans/TestPlanTests.cs b/tests/Allure.Xunit.v3.Tests/TestPlans/TestPlanTests.cs new file mode 100644 index 00000000..64137a2b --- /dev/null +++ b/tests/Allure.Xunit.v3.Tests/TestPlans/TestPlanTests.cs @@ -0,0 +1,194 @@ +using Allure.Testing; +using Allure.Testing.Assertions.Model; +using Allure.Testing.Execution; + +namespace Allure.Xunit.v3.Tests.TestPlans; + +class TestPlanTests +{ + [Test] + public async Task TestPlanSelectorShouldFilterTests(CancellationToken token) + { + var fullName = "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan:" + + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.SecondTest()"; + var results = await RunWithTestPlan( + $$""" + { + "tests": [ + { + "selector": "{{fullName}}" + } + ] + } + """, + token + ); + + await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.SecondTest" + ); + } + + [Test] + public async Task TestPlanAllureIdShouldFilterTests(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"3001"}]}""", + token + ); + + await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.AllureIdTest" + ) + .With.SingleLabel("ALLURE_ID") + .That.HasValue("3001"); + } + + [Test] + public async Task AllureIdPreFilterShouldAvoidConstructingUnselectedTests(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"3002"}]}""", + AllureSampleRegistry.AllureIdPreFilter, + token + ); + + await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.TestPlans.AllureIdPreFilter.SelectedMarkerClass.TestMethod" + ) + .With.SingleLabel("ALLURE_ID") + .That.HasValue("3002"); + + await Assert.That(results).HasSingleGlobals() + .With.SingleError() + .That.HasMessage("selected Allure ID test was constructed"); + } + + [Test] + public async Task AllureIdRegistryShouldHandleDuplicateIds(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"3004"}]}""", + AllureSampleRegistry.DuplicateAllureIds, + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(2); + await Assert.That(results).HasTestResults([ + tr => tr + .HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.DuplicateAllureIds.TestClass.FirstSelectedTest"), + tr => tr + .HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.DuplicateAllureIds.TestClass.SecondSelectedTest"), + ]); + } + + [Test] + public async Task AllureIdRegistryShouldFilterTheories(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"3005"}]}""", + AllureSampleRegistry.AllureIdTheory, + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(2); + await Assert.That(results).HasTestResults([ + tr => tr + .HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.AllureIdTheory.TestClass.SelectedTheory(value: \"foo\")"), + tr => tr + .HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.AllureIdTheory.TestClass.SelectedTheory(value: \"bar\")"), + ]); + } + + [Test] + public async Task RuntimeGuardShouldSkipUnselectedTestWhenPreFilteringIsDisabled(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"404"}]}""", + AllureSampleRegistry.RuntimeGuard, + token + ); + + await Assert.That(results.TestResults).IsEmpty(); + } + + [Test] + public async Task UnmatchedTestPlanShouldProduceNoResults(CancellationToken token) + { + var results = await RunWithTestPlan( + """{"tests":[{"id":"404"}]}""", + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(0); + await Assert.That(results.Containers).Count().IsEqualTo(0); + await Assert.That(results.Globals).Count().IsEqualTo(0); + } + + [Test] + public async Task TestPlanSelectorWithParametersShouldBeCroppedForXunitFilter(CancellationToken token) + { + var fullName = "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan:" + + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.ParameterizedTheory(System.String)"; + + var results = await RunWithTestPlan( + $$"""{"tests":[{"selector":"{{fullName}}"}]}""", + token + ); + + await Assert.That(results.TestResults).Count().IsEqualTo(2); + await Assert.That(results) + .HasTestResults([ + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.ParameterizedTheory(value: \"foo\")"), + (tr) => tr.HasName("Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.ParameterizedTheory(value: \"bar\")"), + ]); + } + + [Test] + public async Task TestPlanSelectorWithTypeParametersShouldBeCroppedForXunitFilter(CancellationToken token) + { + var fullName = "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan:" + + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.GenericTheory[T](T)"; + + var results = await RunWithTestPlan( + $$"""{"tests":[{"selector":"{{fullName}}"}]}""", + token + ); + + await Assert.That(results).HasSingleTestResult( + "Allure.Xunit.v3.Tests.Samples.TestPlans.TestPlan.TestClass.GenericTheory(value: \"baz\")" + ) + .With.FullName(fullName); + } + + static async Task RunWithTestPlan( + string testPlanJson, + CancellationToken token + ) => + await RunWithTestPlan(testPlanJson, AllureSampleRegistry.TestPlan, token); + + static async Task RunWithTestPlan( + string testPlanJson, + AllureSampleRegistryEntry sample, + CancellationToken token + ) + { + var testPlanPath = Path.Combine( + Path.GetTempPath(), + $"allure-testplan-{Guid.NewGuid():N}.json" + ); + + await File.WriteAllTextAsync(testPlanPath, testPlanJson, token); + try + { + return await AllureSampleRunner.RunAsync(sample, new() + { + EnvironmentVariables = { ["ALLURE_TESTPLAN_PATH"] = testPlanPath }, + }, token); + } + finally + { + File.Delete(testPlanPath); + } + } +} diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 69157250..3d89c5e1 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -140,6 +140,7 @@ Samples="@(AllureSample)" RootDirectory="$(_Allure_RepoRoot)" ProjectDirectory="$(MSBuildProjectDirectory)" + TestingPlatform="$(Allure_TestingPlatform)" SampleSolutionDirectory="$(Allure_SampleSolutionDir)" SampleSolutionName="$(Allure_SampleSolutionName)" RootNamespace="$(RootNamespace)" @@ -246,6 +247,7 @@ SampleSources="@(_Allure_ResolvedSample)" SamplePackageReferences="@(SamplePackageReference)" SampleProjectReferences="@(SampleProjectReference)" + SampleProperties="@(SampleProperty)" ProjectDirectory="$(MSBuildProjectDirectory)" RootNamespace="$(RootNamespace)" TestingPlatform="$(Allure_TestingPlatform)"