| File: xunit\AspNetTestRunner.cs | Web Access |
| Project: src\aspnetcore\src\Testing\src\Microsoft.AspNetCore.InternalTesting.csproj (Microsoft.AspNetCore.InternalTesting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Microsoft.AspNetCore.InternalTesting; internal sealed class AspNetTestRunner : XunitTestRunner { private readonly TestOutputHelper _testOutputHelper; private readonly bool _ownsTestOutputHelper; public AspNetTestRunner( ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource) { // Prioritize using ITestOutputHelper from constructor. if (ConstructorArguments != null) { foreach (var obj in ConstructorArguments) { _testOutputHelper = obj as TestOutputHelper; if (_testOutputHelper != null) { break; } } } // No ITestOutputHelper in constructor so we'll create it ourselves. if (_testOutputHelper == null) { _testOutputHelper = new TestOutputHelper(); _ownsTestOutputHelper = true; } } protected override async Task<Tuple<decimal, string>> InvokeTestAsync(ExceptionAggregator aggregator) { if (_ownsTestOutputHelper) { _testOutputHelper.Initialize(MessageBus, Test); } var retryAttribute = GetRetryAttribute(TestMethod); var result = retryAttribute is null ? await base.InvokeTestAsync(aggregator).ConfigureAwait(false) : await RunTestCaseWithRetryAsync(retryAttribute, aggregator).ConfigureAwait(false); if (_ownsTestOutputHelper) { // Update result with output if we created our own ITestOutputHelper. // The string returned from this method is what VS displays as the test output. result = new Tuple<decimal, string>(result.Item1, _testOutputHelper.Output); _testOutputHelper.Uninitialize(); } return result; } private async Task<Tuple<decimal, string>> RunTestCaseWithRetryAsync(RetryAttribute retryAttribute, ExceptionAggregator aggregator) { var totalTimeTaken = 0m; List<string> messages = new(); var numAttempts = Math.Max(1, retryAttribute.MaxRetries); for (var attempt = 1; attempt <= numAttempts; attempt++) { var result = await base.InvokeTestAsync(aggregator).ConfigureAwait(false); totalTimeTaken += result.Item1; messages.Add(result.Item2); if (!aggregator.HasExceptions) { break; } else if (attempt < numAttempts) { // We can't use the ITestOutputHelper here because there's no active test messages.Add($"[{TestCase.DisplayName}] Attempt {attempt} of {retryAttribute.MaxRetries} failed due to {aggregator.ToException()}"); await Task.Delay(5000).ConfigureAwait(false); aggregator.Clear(); } } return new(totalTimeTaken, string.Join(Environment.NewLine, messages)); } protected override async Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator) { var repeatAttribute = GetRepeatAttribute(TestMethod); if (repeatAttribute == null) { return await InvokeTestMethodCoreAsync(aggregator).ConfigureAwait(false); } var repeatContext = new RepeatContext(repeatAttribute.RunCount); RepeatContext.Current = repeatContext; var timeTaken = 0.0M; for (repeatContext.CurrentIteration = 0; repeatContext.CurrentIteration < repeatContext.Limit; repeatContext.CurrentIteration++) { timeTaken = await InvokeTestMethodCoreAsync(aggregator).ConfigureAwait(false); if (aggregator.HasExceptions) { return timeTaken; } } return timeTaken; } private Task<decimal> InvokeTestMethodCoreAsync(ExceptionAggregator aggregator) { var invoker = new AspNetTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource, _testOutputHelper); return invoker.RunAsync(); } private static RepeatAttribute GetRepeatAttribute(MethodInfo methodInfo) { var attributeCandidate = methodInfo.GetCustomAttribute<RepeatAttribute>(); if (attributeCandidate != null) { return attributeCandidate; } attributeCandidate = methodInfo.DeclaringType.GetCustomAttribute<RepeatAttribute>(); if (attributeCandidate != null) { return attributeCandidate; } return methodInfo.DeclaringType.Assembly.GetCustomAttribute<RepeatAttribute>(); } private static RetryAttribute GetRetryAttribute(MethodInfo methodInfo) { var attributeCandidate = methodInfo.GetCustomAttribute<RetryAttribute>(); if (attributeCandidate != null) { return attributeCandidate; } attributeCandidate = methodInfo.DeclaringType.GetCustomAttribute<RetryAttribute>(); if (attributeCandidate != null) { return attributeCandidate; } return methodInfo.DeclaringType.Assembly.GetCustomAttribute<RetryAttribute>(); } }