Merge branch 'release/3.1' => 'master' (#2382)

This commit is contained in:
Doug Bunting 2019-09-23 12:10:14 -07:00 committed by GitHub
commit b760a9da55
32 changed files with 76 additions and 936 deletions

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Core;
@ -23,14 +24,6 @@ namespace Microsoft.Extensions.Logging.Testing
private static readonly string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH";
private static readonly string LogFileExtension = ".log";
private static readonly int MaxPathLength = GetMaxPathLength();
private static char[] InvalidFileChars = new char[]
{
'\"', '<', '>', '|', '\0',
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
(char)31, ':', '*', '?', '\\', '/', ' ', (char)127
};
private static readonly object _lock = new object();
private static readonly Dictionary<Assembly, AssemblyTestLog> _logs = new Dictionary<Assembly, AssemblyTestLog>();
@ -113,8 +106,8 @@ namespace Microsoft.Extensions.Logging.Testing
SerilogLoggerProvider serilogLoggerProvider = null;
if (!string.IsNullOrEmpty(_baseDirectory))
{
logOutputDirectory = Path.Combine(GetAssemblyBaseDirectory(_baseDirectory, _assembly), className);
testName = RemoveIllegalFileChars(testName);
logOutputDirectory = Path.Combine(_baseDirectory, className);
testName = TestFileOutputContext.RemoveIllegalFileChars(testName);
if (logOutputDirectory.Length + testName.Length + LogFileExtension.Length >= MaxPathLength)
{
@ -184,10 +177,10 @@ namespace Microsoft.Extensions.Logging.Testing
{
var logStart = DateTimeOffset.UtcNow;
SerilogLoggerProvider serilogLoggerProvider = null;
var globalLogDirectory = GetAssemblyBaseDirectory(baseDirectory, assembly);
if (!string.IsNullOrEmpty(globalLogDirectory))
if (!string.IsNullOrEmpty(baseDirectory))
{
var globalLogFileName = Path.Combine(globalLogDirectory, "global.log");
baseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly, baseDirectory);
var globalLogFileName = Path.Combine(baseDirectory, "global.log");
serilogLoggerProvider = ConfigureFileLogging(globalLogFileName, logStart);
}
@ -222,31 +215,26 @@ namespace Microsoft.Extensions.Logging.Testing
{
if (!_logs.TryGetValue(assembly, out var log))
{
var baseDirectory = GetFileLoggerAttribute(assembly).BaseDirectory;
var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly);
log = Create(assembly, baseDirectory);
_logs[assembly] = log;
// Try to clear previous logs
var assemblyBaseDirectory = GetAssemblyBaseDirectory(baseDirectory, assembly);
if (Directory.Exists(assemblyBaseDirectory))
// Try to clear previous logs, continue if it fails.
var assemblyBaseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly);
if (!string.IsNullOrEmpty(assemblyBaseDirectory))
{
try
{
Directory.Delete(assemblyBaseDirectory, recursive: true);
}
catch {}
catch { }
}
}
return log;
}
}
private static string GetAssemblyBaseDirectory(string baseDirectory, Assembly assembly)
=> string.IsNullOrEmpty(baseDirectory)
? string.Empty
: Path.Combine(baseDirectory, assembly.GetName().Name, GetFileLoggerAttribute(assembly).TFM);
private static TestFrameworkFileLoggerAttribute GetFileLoggerAttribute(Assembly assembly)
=> assembly.GetCustomAttribute<TestFrameworkFileLoggerAttribute>()
?? throw new InvalidOperationException($"No {nameof(TestFrameworkFileLoggerAttribute)} found on the assembly {assembly.GetName().Name}. "
@ -275,27 +263,6 @@ namespace Microsoft.Extensions.Logging.Testing
return new SerilogLoggerProvider(serilogger, dispose: true);
}
private static string RemoveIllegalFileChars(string s)
{
var sb = new StringBuilder();
foreach (var c in s)
{
if (InvalidFileChars.Contains(c))
{
if (sb.Length > 0 && sb[sb.Length - 1] != '_')
{
sb.Append('_');
}
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
public void Dispose()
{
(_serviceProvider as IDisposable)?.Dispose();

View File

@ -2,6 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
namespace Microsoft.Extensions.Logging.Testing
{
@ -12,7 +17,23 @@ namespace Microsoft.Extensions.Logging.Testing
/// This currently only works in Windows environments
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CollectDumpAttribute : Attribute
public class CollectDumpAttribute : Attribute, ITestMethodLifecycle
{
public Task OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
{
if (exception != null)
{
var path = Path.Combine(context.FileOutput.TestClassOutputDirectory, context.FileOutput.GetUniqueFileName(context.FileOutput.TestName, ".dmp"));
var process = Process.GetCurrentProcess();
DumpCollector.Collect(process, path);
}
return Task.CompletedTask;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Testing;
using Xunit.Abstractions;
namespace Microsoft.Extensions.Logging.Testing
@ -18,6 +19,6 @@ namespace Microsoft.Extensions.Logging.Testing
// For back compat
IDisposable StartLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, string testName);
void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper);
void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper);
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Microsoft.AspNetCore.Testing;
using Xunit.Abstractions;
namespace Microsoft.Extensions.Logging.Testing
@ -13,9 +14,9 @@ namespace Microsoft.Extensions.Logging.Testing
public ITestSink TestSink { get; set; }
public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
{
base.Initialize(methodInfo, testMethodArguments, testOutputHelper);
base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper);
TestSink = new TestSink();
LoggerFactory.AddProvider(new TestLoggerProvider(TestSink));

View File

@ -6,12 +6,16 @@ using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Xunit.Abstractions;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestBase : ILoggedTest
public class LoggedTestBase : ILoggedTest, ITestMethodLifecycle
{
private ExceptionDispatchInfo _initializationException;
@ -23,11 +27,11 @@ namespace Microsoft.Extensions.Logging.Testing
TestOutputHelper = output;
}
protected TestContext Context { get; private set; }
// Internal for testing
internal string ResolvedTestClassName { get; set; }
internal RepeatContext RepeatContext { get; set; }
public string ResolvedLogOutputDirectory { get; set; }
public string ResolvedTestMethodName { get; set; }
@ -49,7 +53,7 @@ namespace Microsoft.Extensions.Logging.Testing
return AssemblyTestLog.ForAssembly(GetType().GetTypeInfo().Assembly).StartTestLog(TestOutputHelper, GetType().FullName, out loggerFactory, minLogLevel, testName);
}
public virtual void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
public virtual void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
{
try
{
@ -59,25 +63,22 @@ namespace Microsoft.Extensions.Logging.Testing
var logLevelAttribute = methodInfo.GetCustomAttribute<LogLevelAttribute>()
?? methodInfo.DeclaringType.GetCustomAttribute<LogLevelAttribute>()
?? methodInfo.DeclaringType.Assembly.GetCustomAttribute<LogLevelAttribute>();
var testName = testMethodArguments.Aggregate(methodInfo.Name, (a, b) => $"{a}-{(b ?? "null")}");
var useShortClassName = methodInfo.DeclaringType.GetCustomAttribute<ShortClassNameAttribute>()
?? methodInfo.DeclaringType.Assembly.GetCustomAttribute<ShortClassNameAttribute>();
// internal for testing
ResolvedTestClassName = useShortClassName == null ? classType.FullName : classType.Name;
ResolvedTestClassName = context.FileOutput.TestClassName;
_testLog = AssemblyTestLog
.ForAssembly(classType.GetTypeInfo().Assembly)
.StartTestLog(
TestOutputHelper,
ResolvedTestClassName,
context.FileOutput.TestClassName,
out var loggerFactory,
logLevelAttribute?.LogLevel ?? LogLevel.Debug,
out var resolvedTestName,
out var logOutputDirectory,
testName);
out var logDirectory,
context.FileOutput.TestName);
ResolvedLogOutputDirectory = logOutputDirectory;
ResolvedLogOutputDirectory = logDirectory;
ResolvedTestMethodName = resolvedTestName;
LoggerFactory = loggerFactory;
@ -91,7 +92,7 @@ namespace Microsoft.Extensions.Logging.Testing
public virtual void Dispose()
{
if(_testLog == null)
if (_testLog == null)
{
// It seems like sometimes the MSBuild goop that adds the test framework can end up in a bad state and not actually add it
// Not sure yet why that happens but the exception isn't clear so I'm adding this error so we can detect it better.
@ -102,5 +103,19 @@ namespace Microsoft.Extensions.Logging.Testing
_initializationException?.Throw();
_testLog.Dispose();
}
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
{
Context = context;
Initialize(context, context.TestMethod, context.MethodArguments, context.Output);
return Task.CompletedTask;
}
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Logging.Testing
{
public class RepeatContext
{
internal int Limit { get; set; }
internal int CurrentIteration { get; set; }
}
}

View File

@ -1,12 +0,0 @@
// Copyright(c) .NET Foundation.All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.Logging.Testing
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)]
public class ShortClassNameAttribute : Attribute
{
}
}

View File

@ -1,20 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Testing;
namespace Microsoft.Extensions.Logging.Testing
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public class TestFrameworkFileLoggerAttribute : Attribute
public class TestFrameworkFileLoggerAttribute : TestOutputDirectoryAttribute
{
public TestFrameworkFileLoggerAttribute(string tfm, string baseDirectory = null)
: base(tfm, baseDirectory)
{
TFM = tfm;
BaseDirectory = baseDirectory;
}
public string TFM { get; }
public string BaseDirectory { get; }
}
}
}

View File

@ -1,28 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Testing;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedConditionalFactDiscoverer : LoggedFactDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
public LoggedConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
{
var skipReason = testMethod.EvaluateSkipConditions();
return skipReason != null
? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod)
: base.CreateTestCase(discoveryOptions, testMethod, factAttribute);
}
}
}

View File

@ -1,55 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Testing;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedConditionalTheoryDiscoverer : LoggedTheoryDiscoverer
{
public LoggedConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink)
: base(diagnosticMessageSink)
{
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod,
IAttributeInfo theoryAttribute)
{
var skipReason = testMethod.EvaluateSkipConditions();
return skipReason != null
? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) }
: base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute);
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod, IAttributeInfo theoryAttribute,
object[] dataRow)
{
var skipReason = testMethod.EvaluateSkipConditions();
if (skipReason == null && dataRow?.Length > 0)
{
var obj = dataRow[0];
if (obj != null)
{
var type = obj.GetType();
var property = type.GetProperty("Skip");
if (property != null && property.PropertyType.Equals(typeof(string)))
{
skipReason = property.GetValue(obj) as string;
}
}
}
return skipReason != null
? base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason)
: base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow);
}
}
}

View File

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedFactDiscoverer : FactDiscoverer
{
public LoggedFactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
}
protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
=> new LoggedTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod);
}
}

View File

@ -1,31 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestAssemblyRunner : XunitTestAssemblyRunner
{
public LoggedTestAssemblyRunner(
ITestAssembly testAssembly,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
{
}
protected override Task<RunSummary> RunTestCollectionAsync(
IMessageBus messageBus,
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
CancellationTokenSource cancellationTokenSource)
=> new LoggedTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
}
}

View File

@ -1,37 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestCase : XunitTestCase
{
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public LoggedTestCase() : base()
{
}
public LoggedTestCase(
IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod,
object[] testMethodArguments = null)
: base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments)
{
}
public override Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new LoggedTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource).RunAsync();
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestCaseRunner : XunitTestCaseRunner
{
public LoggedTestCaseRunner(
IXunitTestCase testCase,
string displayName,
string skipReason,
object[] constructorArguments,
object[] testMethodArguments,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource)
{
}
protected override XunitTestRunner CreateTestRunner(
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
string skipReason,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new LoggedTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments,
skipReason, beforeAfterAttributes, new ExceptionAggregator(aggregator), cancellationTokenSource);
}
}

View File

@ -1,36 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestClassRunner : XunitTestClassRunner
{
public LoggedTestClassRunner(
ITestClass testClass,
IReflectionTypeInfo @class,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
IDictionary<Type, object> collectionFixtureMappings)
: base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings)
{
}
protected override Task<RunSummary> RunTestMethodAsync(
ITestMethod testMethod,
IReflectionMethodInfo method,
IEnumerable<IXunitTestCase> testCases,
object[] constructorArguments)
=> new LoggedTestMethodRunner(testMethod, Class, method, testCases, DiagnosticMessageSink, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, constructorArguments).RunAsync();
}
}

View File

@ -1,33 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestCollectionRunner : XunitTestCollectionRunner
{
private readonly IMessageSink _diagnosticMessageSink;
public LoggedTestCollectionRunner(
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
{
// Base class doesn't expose this, so capture it here.
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
=> new LoggedTestClassRunner(testClass, @class, testCases, _diagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings).RunAsync();
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestFramework : XunitTestFramework
{
public LoggedTestFramework(IMessageSink messageSink) : base(messageSink)
{
}
protected override ITestFrameworkDiscoverer CreateDiscoverer(IAssemblyInfo assemblyInfo)
{
return new LoggedTestFrameworkDiscoverer(assemblyInfo, SourceInformationProvider, DiagnosticMessageSink);
}
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
{
return new LoggedTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
}
}
}

View File

@ -1,80 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestFrameworkDiscoverer : XunitTestFrameworkDiscoverer
{
private IDictionary<Type, IXunitTestCaseDiscoverer> Discoverers { get; }
public LoggedTestFrameworkDiscoverer(
IAssemblyInfo assemblyInfo,
ISourceInformationProvider sourceProvider,
IMessageSink diagnosticMessageSink,
IXunitTestCollectionFactory collectionFactory = null)
: base(assemblyInfo, sourceProvider, diagnosticMessageSink, collectionFactory)
{
Discoverers = new Dictionary<Type, IXunitTestCaseDiscoverer>()
{
{ typeof(ConditionalTheoryAttribute), new LoggedConditionalTheoryDiscoverer(diagnosticMessageSink) },
{ typeof(ConditionalFactAttribute), new LoggedConditionalFactDiscoverer(diagnosticMessageSink) },
{ typeof(TheoryAttribute), new LoggedTheoryDiscoverer(diagnosticMessageSink) },
{ typeof(FactAttribute), new LoggedFactDiscoverer(diagnosticMessageSink) }
};
}
protected override bool FindTestsForMethod(
ITestMethod testMethod,
bool includeSourceInformation,
IMessageBus messageBus,
ITestFrameworkDiscoveryOptions discoveryOptions)
{
if (typeof(ILoggedTest).IsAssignableFrom(testMethod.TestClass.Class.ToRuntimeType()))
{
var factAttributes = testMethod.Method.GetCustomAttributes(typeof(FactAttribute));
if (factAttributes.Count() > 1)
{
var message = $"Test method '{testMethod.TestClass.Class.Name}.{testMethod.Method.Name}' has multiple [Fact]-derived attributes";
var testCase = new ExecutionErrorTestCase(DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, testMethod, message);
return ReportDiscoveredTestCase(testCase, includeSourceInformation, messageBus);
}
var factAttribute = factAttributes.FirstOrDefault();
if (factAttribute == null)
{
return true;
}
var factAttributeType = (factAttribute as IReflectionAttributeInfo)?.Attribute.GetType();
if (!Discoverers.TryGetValue(factAttributeType, out var discoverer))
{
return base.FindTestsForMethod(testMethod, includeSourceInformation, messageBus, discoveryOptions);
}
else
{
foreach (var testCase in discoverer.Discover(discoveryOptions, testMethod, factAttribute))
{
if (!ReportDiscoveredTestCase(testCase, includeSourceInformation, messageBus))
{
return false;
}
}
return true;
}
}
else
{
return base.FindTestsForMethod(testMethod, includeSourceInformation, messageBus, discoveryOptions);
}
}
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestFrameworkExecutor : XunitTestFrameworkExecutor
{
public LoggedTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
: base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
{
}
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
{
using (var assemblyRunner = new LoggedTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions))
{
await assemblyRunner.RunAsync();
}
}
}
}

View File

@ -1,80 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestInvoker : XunitTestInvoker
{
private readonly ITestOutputHelper _output;
private readonly RepeatContext _repeatContext;
private readonly bool _collectDumpOnFailure;
public LoggedTestInvoker(
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
ITestOutputHelper output,
RepeatContext repeatContext,
bool collectDumpOnFailure)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
{
_output = output;
_repeatContext = repeatContext;
_collectDumpOnFailure = collectDumpOnFailure;
}
protected override object CreateTestClass()
{
var testClass = base.CreateTestClass();
(testClass as ILoggedTest).Initialize(
TestMethod,
TestMethodArguments,
_output ?? ConstructorArguments.SingleOrDefault(a => typeof(ITestOutputHelper).IsAssignableFrom(a.GetType())) as ITestOutputHelper);
if (testClass is LoggedTestBase loggedTestBase)
{
// Used for testing
loggedTestBase.RepeatContext = _repeatContext;
}
return testClass;
}
protected override object CallTestMethod(object testClassInstance)
{
try
{
return base.CallTestMethod(testClassInstance);
}
catch
{
if (_collectDumpOnFailure && testClassInstance is LoggedTestBase loggedTestBase)
{
var path = Path.Combine(loggedTestBase.ResolvedLogOutputDirectory, loggedTestBase.ResolvedTestMethodName + ".dmp");
var process = Process.GetCurrentProcess();
DumpCollector.Collect(process, path);
}
throw;
}
}
}
}

View File

@ -1,36 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestMethodRunner : XunitTestMethodRunner
{
private IMessageSink DiagnosticMessageSink { get; }
private object[] ConstructorArguments { get; }
public LoggedTestMethodRunner(
ITestMethod testMethod,
IReflectionTypeInfo @class,
IReflectionMethodInfo method,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
object[] constructorArguments)
: base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments)
{
DiagnosticMessageSink = diagnosticMessageSink;
ConstructorArguments = constructorArguments;
}
protected override Task<RunSummary> RunTestCaseAsync(IXunitTestCase testCase)
=> testCase.RunAsync(DiagnosticMessageSink, MessageBus, ConstructorArguments, new ExceptionAggregator(Aggregator), CancellationTokenSource);
}
}

View File

@ -1,114 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTestRunner : XunitTestRunner
{
public LoggedTestRunner(
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)
{
}
protected async override Task<Tuple<decimal, string>> InvokeTestAsync(ExceptionAggregator aggregator)
{
var testOutputHelper = ConstructorArguments.SingleOrDefault(a => typeof(TestOutputHelper).IsAssignableFrom(a.GetType())) as TestOutputHelper
?? new TestOutputHelper();
testOutputHelper.Initialize(MessageBus, Test);
var executionTime = await InvokeTestMethodAsync(aggregator, testOutputHelper);
var output = testOutputHelper.Output;
testOutputHelper.Uninitialize();
return Tuple.Create(executionTime, output);
}
protected override Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
=> InvokeTestMethodAsync(aggregator, null);
private async Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator, ITestOutputHelper output)
{
var collectDump = TestMethod.GetCustomAttribute<CollectDumpAttribute>() != null;
var repeatAttribute = GetRepeatAttribute(TestMethod);
if (!typeof(LoggedTestBase).IsAssignableFrom(TestClass) || repeatAttribute == null)
{
return await new LoggedTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource, output, null, collectDump).RunAsync();
}
return await RunRepeatTestInvoker(aggregator, output, collectDump, repeatAttribute);
}
private async Task<decimal> RunRepeatTestInvoker(ExceptionAggregator aggregator, ITestOutputHelper output, bool collectDump, RepeatAttribute repeatAttribute)
{
var repeatContext = new RepeatContext
{
Limit = repeatAttribute.RunCount
};
var timeTaken = 0.0M;
var testLogger = new LoggedTestInvoker(
Test,
MessageBus,
TestClass,
ConstructorArguments,
TestMethod,
TestMethodArguments,
BeforeAfterAttributes,
aggregator,
CancellationTokenSource,
output,
repeatContext,
collectDump);
for (repeatContext.CurrentIteration = 0; repeatContext.CurrentIteration < repeatContext.Limit; repeatContext.CurrentIteration++)
{
timeTaken = await testLogger.RunAsync();
if (aggregator.HasExceptions)
{
return timeTaken;
}
}
return timeTaken;
}
private 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>();
}
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTheoryDiscoverer : TheoryDiscoverer
{
public LoggedTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod,
IAttributeInfo theoryAttribute,
object[] dataRow)
=> new[] { new LoggedTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, dataRow) };
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod,
IAttributeInfo theoryAttribute)
=> new[] { new LoggedTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) };
}
}

View File

@ -1,36 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTheoryTestCase : XunitTheoryTestCase
{
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public LoggedTheoryTestCase() : base()
{
}
public LoggedTheoryTestCase(
IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod)
: base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod)
{
}
public override Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new LoggedTheoryTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource).RunAsync();
}
}

View File

@ -1,41 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Extensions.Logging.Testing
{
public class LoggedTheoryTestCaseRunner : XunitTheoryTestCaseRunner
{
public LoggedTheoryTestCaseRunner(
IXunitTestCase testCase,
string displayName,
string skipReason,
object[] constructorArguments,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCase, displayName, skipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource)
{
}
protected override XunitTestRunner CreateTestRunner(
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
string skipReason,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new LoggedTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, new ExceptionAggregator(aggregator), cancellationTokenSource);
}
}

View File

@ -1,28 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
namespace Microsoft.Extensions.Logging.Testing
{
/// <summary>
/// Runs a test multiple times to stress flaky tests that are believed to be fixed.
/// This can be used on an assembly, class, or method name.
/// Requires using <see cref="LoggedTest"/> to run.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)]
public class RepeatAttribute : Attribute
{
public RepeatAttribute(int runCount = 10)
{
RunCount = runCount;
}
/// <summary>
/// The number of times to run a test.
/// </summary>
public int RunCount { get; }
}
}

View File

@ -11,8 +11,8 @@
Condition="'$(GenerateLoggingTestingAssemblyAttributes)' != 'false'">
<ItemGroup>
<AssemblyAttribute Include="Xunit.TestFramework">
<_Parameter1>Microsoft.Extensions.Logging.Testing.LoggedTestFramework</_Parameter1>
<_Parameter2>Microsoft.Extensions.Logging.Testing</_Parameter2>
<_Parameter1>Microsoft.AspNetCore.Testing.AspNetTestFramework</_Parameter1>
<_Parameter2>Microsoft.AspNetCore.Testing</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="Microsoft.Extensions.Logging.Testing.TestFrameworkFileLoggerAttribute">

View File

@ -18,12 +18,6 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
private static readonly string ThisAssemblyName = ThisAssembly.GetName().Name;
private static readonly string TFM = new DirectoryInfo(AppContext.BaseDirectory).Name;
[Fact]
public void FullClassNameUsedWhenShortClassNameAttributeNotSpecified()
{
Assert.Equal(GetType().FullName, ResolvedTestClassName);
}
[Fact]
public void ForAssembly_ReturnsSameInstanceForSameAssembly()
{
@ -57,7 +51,7 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
}
[Fact]
private Task TestLogEscapesIllegalFileNames() =>
public Task TestLogEscapesIllegalFileNames() =>
RunTestLogFunctionalTest((tempDir) =>
{
var illegalTestName = "T:e/s//t";

View File

@ -1,43 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.Extensions.Logging.Testing.Tests
{
[Repeat]
public class LoggedTestXunitRepeatTests : LoggedTest
{
public static int _runCount = 0;
[Fact]
[Repeat(5)]
public void RepeatLimitIsSetCorrectly()
{
Assert.Equal(5, RepeatContext.Limit);
}
[Fact]
[Repeat(5)]
public void RepeatRunsTestSpecifiedNumberOfTimes()
{
Assert.Equal(RepeatContext.CurrentIteration, _runCount);
_runCount++;
}
[Fact]
public void RepeatCanBeSetOnClass()
{
Assert.Equal(10, RepeatContext.Limit);
}
}
public class LoggedTestXunitRepeatAssemblyTests : LoggedTest
{
[Fact]
public void RepeatCanBeSetOnAssembly()
{
Assert.Equal(1, RepeatContext.Limit);
}
}
}

View File

@ -21,18 +21,6 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
_output = output;
}
[Fact]
public void ShortClassNameUsedWhenShortClassNameAttributeSpecified()
{
Assert.Equal(GetType().Name, ResolvedTestClassName);
}
[Fact]
public void LoggedTestTestOutputHelperSameInstanceAsInjectedConstructorArg()
{
Assert.Same(_output, TestOutputHelper);
}
[Fact]
public void LoggedFactInitializesLoggedTestProperties()
{
@ -189,9 +177,9 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
public bool SetupInvoked { get; private set; } = false;
public bool ITestOutputHelperIsInitialized { get; private set; } = false;
public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
{
base.Initialize(methodInfo, testMethodArguments, testOutputHelper);
base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper);
try
{

View File

@ -2,4 +2,3 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
[assembly: LogLevel(LogLevel.Trace)]
[assembly: Repeat(1)]