Merge branches

\n\nCommit migrated from 9356c2770b
This commit is contained in:
wtgodbe 2019-09-27 13:13:49 -07:00
commit fb73cc1d81
80 changed files with 1397 additions and 105 deletions

View File

@ -1,15 +1,15 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs" />
<Reference Include="Microsoft.Extensions.Configuration" />
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp.cs" />
<Reference Include="Microsoft.Extensions.Configuration" />
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
</ItemGroup>

View File

@ -2,8 +2,8 @@
<PropertyGroup>
<Description>Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration.</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<IsPackable>true</IsPackable>
<IsShipping>true</IsShipping>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,14 +1,14 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs" />
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.FileProviders.Embedded.netcoreapp.cs" />
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
</ItemGroup>
</Project>

View File

@ -3,10 +3,10 @@
<PropertyGroup>
<RootNamespace>Microsoft.Extensions.FileProviders</RootNamespace>
<Description>File provider for files in embedded resources for Microsoft.Extensions.FileProviders.</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NuspecFile>$(MSBuildProjectName).multitarget.nuspec</NuspecFile>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<NuspecFile Condition="'$(DotNetBuildFromSource)' == 'true'">$(MSBuildProjectName).netcoreapp3.0.nuspec</NuspecFile>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NuspecFile Condition="'$(DotNetBuildFromSource)' == 'true'">$(MSBuildProjectName).netcoreapp.nuspec</NuspecFile>
<IsPackable>true</IsPackable>
<IsShipping>true</IsShipping>
</PropertyGroup>
@ -27,9 +27,7 @@
<ItemGroup>
<NuspecProperty Include="AssemblyName=$(AssemblyName)" />
<NuspecProperty Include="OutputBinary=$(OutputPath)**\$(AssemblyName).dll" />
<NuspecProperty Include="OutputSymbol=$(OutputPath)**\$(AssemblyName).pdb" />
<NuspecProperty Include="OutputDocumentation=$(OutputPath)**\$(AssemblyName).xml" />
<NuspecProperty Include="OutputPath=$(OutputPath)" />
<NuspecProperty Include="TaskAssemblyNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll"/>
<NuspecProperty Include="TaskSymbolNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb" Condition="'$(DebugType)'!='embedded'"/>
<NuspecProperty Include="PackageIcon=$(PackageIconFullPath)" />

View File

@ -3,7 +3,7 @@
<metadata>
$CommonMetadataElements$
<dependencies>
<group targetFramework=".NETCoreApp3.0">
<group targetFramework=".NETCoreApp3.1">
<dependency id="Microsoft.Extensions.FileProviders.Abstractions" version="$version$" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard2.0">
@ -14,9 +14,9 @@
</metadata>
<files>
<file src="$OutputBinary$" target="lib\" />
<file src="$OutputSymbol$" target="lib\" />
<file src="$OutputDocumentation$" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.dll" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.pdb" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.xml" target="lib\" />
<file src="build\**\*" target="build\" />
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting\" />
<file src="$PackageIcon$" target="" />

View File

@ -3,7 +3,7 @@
<metadata>
$CommonMetadataElements$
<dependencies>
<group targetFramework=".NETCoreApp3.0">
<group targetFramework=".NETCoreApp3.1">
<dependency id="Microsoft.Extensions.FileProviders.Abstractions" version="$version$" exclude="Build,Analyzers" />
</group>
</dependencies>
@ -11,9 +11,9 @@
</metadata>
<files>
<file src="$OutputBinary$" target="lib\" />
<file src="$OutputSymbol$" target="lib\" />
<file src="$OutputDocumentation$" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.dll" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.pdb" target="lib\" />
<file src="$OutputPath$**\$AssemblyName$.xml" target="lib\" />
<file src="build\**\*" target="build\" />
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting\" />
<file src="$PackageIcon$" target="" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -1,14 +1,14 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp.cs" />
</ItemGroup>
</Project>

View File

@ -7,8 +7,8 @@ Commonly Used Types
Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
</Description>
<RootNamespace>Microsoft.Extensions.Diagnostics.HealthChecks</RootNamespace>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>diagnostics;healthchecks</PackageTags>

View File

@ -1,7 +1,7 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs" />
@ -9,8 +9,8 @@
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp.cs" />
<Reference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" />
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />

View File

@ -6,8 +6,8 @@ Commonly Used Types:
Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService
Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>diagnostics;healthchecks</PackageTags>

View File

@ -3,7 +3,7 @@
<Import Project="$(RepoRoot)src\Logging\Logging.Testing\src\build\Microsoft.Extensions.Logging.Testing.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<RootNamespace>Microsoft.Extensions.Diagnostics.HealthChecks</RootNamespace>
</PropertyGroup>

View File

@ -1,14 +1,14 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.JSInterop.netstandard2.0.cs" />
<Reference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.JSInterop.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.JSInterop.netcoreapp.cs" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,8 @@
// 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.JSInterop
{
/// <summary>
@ -15,6 +17,11 @@ namespace Microsoft.JSInterop
/// <returns>An instance of <see cref="DotNetObjectReference{TValue}" />.</returns>
public static DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
return new DotNetObjectReference<TValue>(value);
}
}

View File

@ -24,6 +24,9 @@ namespace Microsoft.JSInterop.Infrastructure
private static readonly ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
= new ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
private static readonly ConcurrentDictionary<Type, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByType
= new ConcurrentDictionary<Type, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
/// <summary>
/// Receives a call from JS to .NET, locating and invoking the specified method.
/// </summary>
@ -129,9 +132,12 @@ namespace Microsoft.JSInterop.Infrastructure
var methodIdentifier = callInfo.MethodIdentifier;
AssemblyKey assemblyKey;
MethodInfo methodInfo;
Type[] parameterTypes;
if (objectReference is null)
{
assemblyKey = new AssemblyKey(assemblyName);
(methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier);
}
else
{
@ -147,11 +153,9 @@ namespace Microsoft.JSInterop.Infrastructure
return default;
}
assemblyKey = new AssemblyKey(objectReference.Value.GetType().Assembly);
(methodInfo, parameterTypes) = GetCachedMethodInfo(objectReference, methodIdentifier);
}
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier);
var suppliedArgs = ParseArguments(jsRuntime, methodIdentifier, argsJson, parameterTypes);
try
@ -301,7 +305,47 @@ namespace Microsoft.JSInterop.Infrastructure
}
else
{
throw new ArgumentException($"The assembly '{assemblyKey.AssemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
throw new ArgumentException($"The assembly '{assemblyKey.AssemblyName}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
}
}
private static (MethodInfo methodInfo, Type[] parameterTypes) GetCachedMethodInfo(IDotNetObjectReference objectReference, string methodIdentifier)
{
var type = objectReference.Value.GetType();
var assemblyMethods = _cachedMethodsByType.GetOrAdd(type, ScanTypeForCallableMethods);
if (assemblyMethods.TryGetValue(methodIdentifier, out var result))
{
return result;
}
else
{
throw new ArgumentException($"The type '{type.Name}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
}
static Dictionary<string, (MethodInfo, Type[])> ScanTypeForCallableMethods(Type type)
{
var result = new Dictionary<string, (MethodInfo, Type[])>(StringComparer.Ordinal);
var invokableMethods = type
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(method => !method.ContainsGenericParameters && method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
foreach (var method in invokableMethods)
{
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier ?? method.Name;
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
if (result.ContainsKey(identifier))
{
throw new InvalidOperationException($"The type {type.Name} contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
$"type must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
}
result.Add(identifier, (method, parameterTypes));
}
return result;
}
}
@ -312,35 +356,22 @@ namespace Microsoft.JSInterop.Infrastructure
var result = new Dictionary<string, (MethodInfo, Type[])>(StringComparer.Ordinal);
var invokableMethods = GetRequiredLoadedAssembly(assemblyKey)
.GetExportedTypes()
.SelectMany(type => type.GetMethods(
BindingFlags.Public |
BindingFlags.DeclaredOnly |
BindingFlags.Instance |
BindingFlags.Static))
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
.SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Static))
.Where(method => !method.ContainsGenericParameters && method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
foreach (var method in invokableMethods)
{
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier ?? method.Name;
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
try
if (result.ContainsKey(identifier))
{
result.Add(identifier, (method, parameterTypes));
}
catch (ArgumentException)
{
if (result.ContainsKey(identifier))
{
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
}
else
{
throw;
}
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
}
result.Add(identifier, (method, parameterTypes));
}
return result;

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<Description>Abstractions and features for interop between .NET and JavaScript code.</Description>
<PackageTags>javascript;interop</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@ -3,7 +3,6 @@
using System;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -70,7 +69,7 @@ namespace Microsoft.JSInterop.Infrastructure
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, default, default), null);
});
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public invokable method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
}
[Fact]
@ -355,6 +354,78 @@ namespace Microsoft.JSInterop.Infrastructure
Assert.Equal("MY STRING", resultDto.StringVal);
}
[Fact]
public void CanInvokeNonGenericInstanceMethodOnGenericType()
{
var jsRuntime = new TestJSRuntime();
var targetInstance = new GenericType<int>();
jsRuntime.Invoke<object>("_setup",
DotNetObjectReference.Create(targetInstance));
var argsJson = "[\"hello world\"]";
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<int>.EchoStringParameter), 1, default), argsJson);
// Assert
Assert.Equal("\"hello world\"", resultJson);
}
[Fact]
public void CanInvokeMethodsThatAcceptGenericParametersOnGenericTypes()
{
var jsRuntime = new TestJSRuntime();
var targetInstance = new GenericType<string>();
jsRuntime.Invoke<object>("_setup",
DotNetObjectReference.Create(targetInstance));
var argsJson = "[\"hello world\"]";
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<string>.EchoParameter), 1, default), argsJson);
// Assert
Assert.Equal("\"hello world\"", resultJson);
}
[Fact]
public void CannotInvokeStaticOpenGenericMethods()
{
var methodIdentifier = "StaticGenericMethod";
var jsRuntime = new TestJSRuntime();
// Act
var ex = Assert.Throws<ArgumentException>(() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, 0, default), "[7]"));
Assert.Contains($"The assembly '{thisAssemblyName}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].", ex.Message);
}
[Fact]
public void CannotInvokeInstanceOpenGenericMethods()
{
var methodIdentifier = "InstanceGenericMethod";
var targetInstance = new GenericType<int>();
var jsRuntime = new TestJSRuntime();
jsRuntime.Invoke<object>("_setup",
DotNetObjectReference.Create(targetInstance));
var argsJson = "[\"hello world\"]";
// Act
var ex = Assert.Throws<ArgumentException>(() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, methodIdentifier, 1, default), argsJson));
Assert.Contains($"The type 'GenericType`1' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].", ex.Message);
}
[Fact]
public void CannotInvokeMethodsWithGenericParameters_IfTypesDoNotMatch()
{
var jsRuntime = new TestJSRuntime();
var targetInstance = new GenericType<int>();
jsRuntime.Invoke<object>("_setup",
DotNetObjectReference.Create(targetInstance));
var argsJson = "[\"hello world\"]";
// Act & Assert
Assert.Throws<JsonException>(() =>
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<int>.EchoParameter), 1, default), argsJson));
}
[Fact]
public void CannotInvokeWithFewerNumberOfParameters()
{
@ -790,6 +861,18 @@ namespace Microsoft.JSInterop.Infrastructure
}
}
public class GenericType<TValue>
{
[JSInvokable] public string EchoStringParameter(string input) => input;
[JSInvokable] public TValue EchoParameter(TValue input) => input;
}
public class GenericMethodClass
{
[JSInvokable("StaticGenericMethod")] public static string StaticGenericMethod<TValue>(TValue input) => input.ToString();
[JSInvokable("InstanceGenericMethod")] public string GenericMethod<TValue>(TValue input) => input.ToString();
}
public class TestJSRuntime : JSInProcessRuntime
{
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,14 +1,14 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.Localization.Abstractions.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.Localization.Abstractions.netcoreapp.cs" />
</ItemGroup>
</Project>

View File

@ -6,8 +6,8 @@
Commonly used types:
Microsoft.Extensions.Localization.IStringLocalizer
Microsoft.Extensions.Localization.IStringLocalizer&lt;T&gt;</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>localization</PackageTags>

View File

@ -1,7 +1,7 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.Localization.netstandard2.0.cs" />
@ -10,8 +10,8 @@
<Reference Include="Microsoft.Extensions.Localization.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.Localization.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.Localization.netcoreapp.cs" />
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
<Reference Include="Microsoft.Extensions.Localization.Abstractions" />

View File

@ -3,8 +3,8 @@
<PropertyGroup>
<Product>Microsoft .NET Extensions</Product>
<Description>Application localization services and default implementation based on ResourceManager to load localized assembly resources.</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>localization</PackageTags>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -402,7 +402,7 @@ namespace Microsoft.Extensions.Internal
}
}
#if NETCOREAPP3_0
#if NETCOREAPP
return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null);
#else
try

View File

@ -30,8 +30,12 @@ namespace BenchmarkDotNet.Attributes
Add(Job.Core
#if NETCOREAPP2_1
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
#else
#elif NETCOREAPP3_0
.With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.0", null, ".NET Core 3.0")))
#elif NETCOREAPP3_1
.With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.1", null, ".NET Core 3.1")))
#else
#error Target frameworks need to be updated.
#endif
.With(new GcMode { Server = true })
.With(RunStrategy.Throughput));

View File

@ -1,7 +1,7 @@
// 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.
#if NETCOREAPP3_0
#if NETCOREAPP
using System.IO;
using System.Runtime.InteropServices;
using Xunit;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Defines a lifecycle for attributes or classes that want to know about tests starting
/// or ending. Implement this on a test class, or attribute at the method/class/assembly level.
/// </summary>
/// <remarks>
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
/// </remarks>
public interface ITestMethodLifecycle
{
Task OnTestStartAsync(TestContext context, CancellationToken cancellationToken);
Task OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,27 @@
// 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.AspNetCore.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 the AspNetCore test framework.
/// </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

@ -0,0 +1,27 @@
// 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.Threading;
namespace Microsoft.AspNetCore.Testing
{
public class RepeatContext
{
private static AsyncLocal<RepeatContext> _current = new AsyncLocal<RepeatContext>();
public static RepeatContext Current
{
get => _current.Value;
internal set => _current.Value = value;
}
public RepeatContext(int limit)
{
Limit = limit;
}
public int Limit { get; }
public int CurrentIteration { get; set; }
}
}

View File

@ -0,0 +1,17 @@
// 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.AspNetCore.Testing
{
/// <summary>
/// Used to specify that <see cref="TestFileOutputContext.TestClassName"/> should used the
/// unqualified class name. This is needed when a fully-qualified class name exceeds
/// max path for logging.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)]
public class ShortClassNameAttribute : Attribute
{
}
}

View File

@ -0,0 +1,44 @@
// 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.Reflection;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Provides access to contextual information about the running tests. Get access by
/// implementing <see cref="ITestMethodLifecycle"/>.
/// </summary>
/// <remarks>
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
/// </remarks>
public sealed class TestContext
{
private Lazy<TestFileOutputContext> _files;
public TestContext(
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] methodArguments,
ITestOutputHelper output)
{
TestClass = testClass;
ConstructorArguments = constructorArguments;
TestMethod = testMethod;
MethodArguments = methodArguments;
Output = output;
_files = new Lazy<TestFileOutputContext>(() => new TestFileOutputContext(this));
}
public Type TestClass { get; }
public MethodInfo TestMethod { get; }
public object[] ConstructorArguments { get; }
public object[] MethodArguments { get; }
public ITestOutputHelper Output { get; }
public TestFileOutputContext FileOutput => _files.Value;
}
}

View File

@ -0,0 +1,140 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Provides access to file storage for the running test. Get access by
/// implementing <see cref="ITestMethodLifecycle"/>, and accessing <see cref="TestContext.FileOutput"/>.
/// </summary>
/// <remarks>
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
/// </remarks>
public sealed class TestFileOutputContext
{
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 readonly TestContext _parent;
public TestFileOutputContext(TestContext parent)
{
_parent = parent;
TestName = GetTestMethodName(parent.TestMethod, parent.MethodArguments);
TestClassName = GetTestClassName(parent.TestClass);
AssemblyOutputDirectory = GetAssemblyBaseDirectory(_parent.TestClass.Assembly);
if (!string.IsNullOrEmpty(AssemblyOutputDirectory))
{
TestClassOutputDirectory = Path.Combine(AssemblyOutputDirectory, TestClassName);
}
}
public string TestName { get; }
public string TestClassName { get; }
public string AssemblyOutputDirectory { get; }
public string TestClassOutputDirectory { get; }
public string GetUniqueFileName(string prefix, string extension)
{
if (prefix == null)
{
throw new ArgumentNullException(nameof(prefix));
}
if (extension != null && !extension.StartsWith(".", StringComparison.Ordinal))
{
throw new ArgumentException("The extension must start with '.' if one is provided.", nameof(extension));
}
var path = Path.Combine(TestClassOutputDirectory, $"{prefix}{extension}");
var i = 1;
while (File.Exists(path))
{
path = Path.Combine(TestClassOutputDirectory, $"{prefix}{i++}{extension}");
}
return path;
}
// Gets the output directory without appending the TFM or assembly name.
public static string GetOutputDirectory(Assembly assembly)
{
var attribute = assembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault();
return attribute?.BaseDirectory;
}
public static string GetAssemblyBaseDirectory(Assembly assembly, string baseDirectory = null)
{
var attribute = assembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault();
baseDirectory = baseDirectory ?? attribute?.BaseDirectory;
if (string.IsNullOrEmpty(baseDirectory))
{
return string.Empty;
}
return Path.Combine(baseDirectory, assembly.GetName().Name, attribute.TargetFramework);
}
public static string GetTestClassName(Type type)
{
var shortNameAttribute =
type.GetCustomAttribute<ShortClassNameAttribute>() ??
type.Assembly.GetCustomAttribute<ShortClassNameAttribute>();
var name = shortNameAttribute == null ? type.FullName : type.Name;
// Try to shorten the class name using the assembly name
var assemblyName = type.Assembly.GetName().Name;
if (name.StartsWith(assemblyName + "."))
{
name = name.Substring(assemblyName.Length + 1);
}
return name;
}
public static string GetTestMethodName(MethodInfo method, object[] arguments)
{
var name = arguments.Aggregate(method.Name, (a, b) => $"{a}-{(b ?? "null")}");
return RemoveIllegalFileChars(name);
}
public 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();
}
}
}

View File

@ -0,0 +1,20 @@
// 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.AspNetCore.Testing
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = true)]
public class TestOutputDirectoryAttribute : Attribute
{
public TestOutputDirectoryAttribute(string targetFramework, string baseDirectory = null)
{
TargetFramework = targetFramework;
BaseDirectory = baseDirectory;
}
public string BaseDirectory { get; }
public string TargetFramework { get; }
}
}

View File

@ -0,0 +1,83 @@
// 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.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner
{
private readonly Dictionary<Type, object> _assemblyFixtureMappings = new Dictionary<Type, object>();
public AspNetTestAssemblyRunner(
ITestAssembly testAssembly,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
{
}
protected override async Task AfterTestAssemblyStartingAsync()
{
await base.AfterTestAssemblyStartingAsync();
// Find all the AssemblyFixtureAttributes on the test assembly
Aggregator.Run(() =>
{
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
.Assembly
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
.Cast<AssemblyFixtureAttribute>()
.ToList();
// Instantiate all the fixtures
foreach (var fixtureAttribute in fixturesAttributes)
{
var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) });
if (ctorWithDiagnostics != null)
{
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
}
else
{
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType);
}
}
});
}
protected override Task BeforeTestAssemblyFinishedAsync()
{
// Dispose fixtures
foreach (var disposable in _assemblyFixtureMappings.Values.OfType<IDisposable>())
{
Aggregator.Run(disposable.Dispose);
}
return base.BeforeTestAssemblyFinishedAsync();
}
protected override Task<RunSummary> RunTestCollectionAsync(
IMessageBus messageBus,
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
CancellationTokenSource cancellationTokenSource)
=> new AspNetTestCollectionRunner(
_assemblyFixtureMappings,
testCollection,
testCases,
DiagnosticMessageSink,
messageBus,
TestCaseOrderer,
new ExceptionAggregator(Aggregator),
cancellationTokenSource).RunAsync();
}
}

View File

@ -0,0 +1,33 @@
// 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.AspNetCore.Testing
{
internal class AspNetTestCaseRunner : XunitTestCaseRunner
{
public AspNetTestCaseRunner(
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)
{
return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
}
}
}

View File

@ -0,0 +1,44 @@
// 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.AspNetCore.Testing
{
internal class AspNetTestClassRunner : XunitTestClassRunner
{
public AspNetTestClassRunner(
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)
{
var runner = new AspNetTestMethodRunner(
testMethod,
Class,
method,
testCases,
DiagnosticMessageSink,
MessageBus,
new ExceptionAggregator(Aggregator),
CancellationTokenSource,
constructorArguments);
return runner.RunAsync();
}
}
}

View File

@ -0,0 +1,75 @@
// 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.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestCollectionRunner : XunitTestCollectionRunner
{
private readonly IDictionary<Type, object> _assemblyFixtureMappings;
private readonly IMessageSink _diagnosticMessageSink;
public AspNetTestCollectionRunner(
Dictionary<Type, object> assemblyFixtureMappings,
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
{
_assemblyFixtureMappings = assemblyFixtureMappings;
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override async Task AfterTestCollectionStartingAsync()
{
await base.AfterTestCollectionStartingAsync();
// note: We pass the assembly fixtures into the runner as ICollectionFixture<> - this seems to work OK without any
// drawbacks. It's reasonable that we could add IAssemblyFixture<> and related plumbing if it ever became required.
//
// The reason for assembly fixture is when we want to start/stop something as the project scope - tests can only be
// in one test collection at a time.
foreach (var mapping in _assemblyFixtureMappings)
{
CollectionFixtureMappings.Add(mapping.Key, mapping.Value);
}
}
protected override Task BeforeTestCollectionFinishedAsync()
{
// We need to remove the assembly fixtures so they won't get disposed.
foreach (var mapping in _assemblyFixtureMappings)
{
CollectionFixtureMappings.Remove(mapping.Key);
}
return base.BeforeTestCollectionFinishedAsync();
}
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
{
var runner = new AspNetTestClassRunner(
testClass,
@class,
testCases,
DiagnosticMessageSink,
MessageBus,
TestCaseOrderer,
new ExceptionAggregator(Aggregator),
CancellationTokenSource,
CollectionFixtureMappings);
return runner.RunAsync();
}
}
}

View File

@ -0,0 +1,20 @@
// 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.AspNetCore.Testing
{
public class AspNetTestFramework : XunitTestFramework
{
public AspNetTestFramework(IMessageSink messageSink)
: base(messageSink)
{
}
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
=> new AspNetTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
}
}

View File

@ -0,0 +1,26 @@
// 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.AspNetCore.Testing
{
public class AspNetTestFrameworkExecutor : XunitTestFrameworkExecutor
{
public AspNetTestFrameworkExecutor(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 AspNetTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions))
{
await assemblyRunner.RunAsync();
}
}
}
}

View File

@ -0,0 +1,84 @@
// 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.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
internal class AspNetTestInvoker : XunitTestInvoker
{
public AspNetTestInvoker(
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
{
}
protected override async Task<decimal> InvokeTestMethodAsync(object testClassInstance)
{
var output = new TestOutputHelper();
output.Initialize(MessageBus, Test);
var context = new TestContext(TestClass, ConstructorArguments, TestMethod, TestMethodArguments, output);
var lifecycleHooks = GetLifecycleHooks(testClassInstance, TestClass, TestMethod);
await Aggregator.RunAsync(async () =>
{
foreach (var lifecycleHook in lifecycleHooks)
{
await lifecycleHook.OnTestStartAsync(context, CancellationTokenSource.Token);
}
});
var time = await base.InvokeTestMethodAsync(testClassInstance);
await Aggregator.RunAsync(async () =>
{
var exception = Aggregator.HasExceptions ? Aggregator.ToException() : null;
foreach (var lifecycleHook in lifecycleHooks)
{
await lifecycleHook.OnTestEndAsync(context, exception, CancellationTokenSource.Token);
}
});
return time;
}
private static IEnumerable<ITestMethodLifecycle> GetLifecycleHooks(object testClassInstance, Type testClass, MethodInfo testMethod)
{
foreach (var attribute in testMethod.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
{
yield return attribute;
}
if (testClassInstance is ITestMethodLifecycle instance)
{
yield return instance;
}
foreach (var attribute in testClass.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
{
yield return attribute;
}
foreach (var attribute in testClass.Assembly.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
{
yield return attribute;
}
}
}
}

View File

@ -0,0 +1,73 @@
// 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 Microsoft.AspNetCore.Testing.xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
internal class AspNetTestMethodRunner : XunitTestMethodRunner
{
private readonly object[] _constructorArguments;
private readonly IMessageSink _diagnosticMessageSink;
public AspNetTestMethodRunner(
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)
{
if (testCase.GetType() == typeof(XunitTestCase))
{
// If we get here this is a 'regular' test case, not something that represents a skipped test.
//
// We can take control of it's invocation thusly.
var runner = new AspNetTestCaseRunner(
testCase,
testCase.DisplayName,
testCase.SkipReason,
_constructorArguments,
testCase.TestMethodArguments,
MessageBus,
new ExceptionAggregator(Aggregator),
CancellationTokenSource);
return runner.RunAsync();
}
if (testCase.GetType() == typeof(XunitTheoryTestCase))
{
// If we get here this is a 'regular' theory test case, not something that represents a skipped test.
//
// We can take control of it's invocation thusly.
var runner = new AspNetTheoryTestCaseRunner(
testCase,
testCase.DisplayName,
testCase.SkipReason,
_constructorArguments,
_diagnosticMessageSink,
MessageBus,
new ExceptionAggregator(Aggregator),
CancellationTokenSource);
return runner.RunAsync();
}
return base.RunTestCaseAsync(testCase);
}
}
}

View File

@ -0,0 +1,78 @@
// 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 System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
internal class AspNetTestRunner : XunitTestRunner
{
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)
{
}
protected override async Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
{
var repeatAttribute = GetRepeatAttribute(TestMethod);
if (repeatAttribute == null)
{
return await InvokeTestMethodCoreAsync(aggregator);
}
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);
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);
return invoker.RunAsync();
}
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

@ -0,0 +1,33 @@
// 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.AspNetCore.Testing.xunit
{
internal class AspNetTheoryTestCaseRunner : XunitTheoryTestCaseRunner
{
public AspNetTheoryTestCaseRunner(
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)
{
return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
}
}
}

View File

@ -0,0 +1,18 @@
// 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.AspNetCore.Testing
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class AssemblyFixtureAttribute : Attribute
{
public AssemblyFixtureAttribute(Type fixtureType)
{
FixtureType = fixtureType;
}
public Type FixtureType { get; private set; }
}
}

View File

@ -1,6 +1,7 @@
// 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 Xunit.Abstractions;
using Xunit.Sdk;
@ -63,5 +64,24 @@ namespace Microsoft.AspNetCore.Testing
base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason)
: base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow);
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkippedDataRow(
ITestFrameworkDiscoveryOptions discoveryOptions,
ITestMethod testMethod,
IAttributeInfo theoryAttribute,
object[] dataRow,
string skipReason)
{
return new[]
{
new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow),
};
}
[Obsolete]
protected override IXunitTestCase CreateTestCaseForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason)
{
return new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow);
}
}
}

View File

@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Testing
/// to <c>xunit.console.exe</c>. Similarly, it can run only flaky tests using <c>-trait "Flaky:AzP:OS:all=true" -trait "Flaky:AzP:OS:Darwin=true"</c>
/// </para>
/// </example>
[TraitDiscoverer("Microsoft.AspNetCore.Testing." + nameof(FlakyTestDiscoverer), "Microsoft.AspNetCore.Testing")]
[TraitDiscoverer("Microsoft.AspNetCore.Testing." + nameof(FlakyTraitDiscoverer), "Microsoft.AspNetCore.Testing")]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public sealed class FlakyAttribute : Attribute, ITraitAttribute
{

View File

@ -6,7 +6,7 @@ using Xunit.Sdk;
// Do not change this namespace without changing the usage in FlakyAttribute
namespace Microsoft.AspNetCore.Testing
{
public class FlakyTestDiscoverer : ITraitDiscoverer
public class FlakyTraitDiscoverer : ITraitDiscoverer
{
public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
{

View File

@ -2,6 +2,8 @@
// 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;
@ -33,8 +35,11 @@ namespace Microsoft.AspNetCore.Testing
public override void Deserialize(IXunitSerializationInfo data)
{
base.Deserialize(data);
_skipReason = data.GetValue<string>(nameof(_skipReason));
// We need to call base after reading our value, because Deserialize will call
// into GetSkipReason.
base.Deserialize(data);
}
public override void Serialize(IXunitSerializationInfo data)

View File

@ -0,0 +1,80 @@
using System;
using System.ComponentModel;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
// This is a workaround for https://github.com/xunit/xunit/issues/1782 - as such, this code is a copy-paste
// from xUnit with the exception of fixing the bug.
//
// This will only work with [ConditionalTheory].
internal class WORKAROUND_SkippedDataRowTestCase : XunitTestCase
{
string skipReason;
/// <summary/>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public WORKAROUND_SkippedDataRowTestCase() { }
/// <summary>
/// Initializes a new instance of the <see cref="XunitSkippedDataRowTestCase"/> class.
/// </summary>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param>
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="skipReason">The reason that this test case will be skipped</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
[Obsolete("Please call the constructor which takes TestMethodDisplayOptions")]
public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
ITestMethod testMethod,
string skipReason,
object[] testMethodArguments = null)
: this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, skipReason, testMethodArguments) { }
/// <summary>
/// Initializes a new instance of the <see cref="XunitSkippedDataRowTestCase"/> class.
/// </summary>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param>
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="defaultMethodDisplayOptions">Default method display options to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="skipReason">The reason that this test case will be skipped</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod,
string skipReason,
object[] testMethodArguments = null)
: base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments)
{
this.skipReason = skipReason;
}
/// <inheritdoc/>
public override void Deserialize(IXunitSerializationInfo data)
{
// SkipReason has to be read before we call base.Deserialize, this is the workaround.
this.skipReason = data.GetValue<string>("SkipReason");
base.Deserialize(data);
}
/// <inheritdoc/>
protected override string GetSkipReason(IAttributeInfo factAttribute)
{
return skipReason;
}
/// <inheritdoc/>
public override void Serialize(IXunitSerializationInfo data)
{
base.Serialize(data);
data.AddValue("SkipReason", skipReason);
}
}
}

View File

@ -0,0 +1,47 @@
// 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.AspNetCore.Testing
{
// We include a collection and assembly fixture to verify that they both still work.
[Collection("MyCollection")]
[TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")]
public class AssemblyFixtureTest
{
public AssemblyFixtureTest(TestAssemblyFixture assemblyFixture, TestCollectionFixture collectionFixture)
{
AssemblyFixture = assemblyFixture;
CollectionFixture = collectionFixture;
}
public TestAssemblyFixture AssemblyFixture { get; }
public TestCollectionFixture CollectionFixture { get; }
[Fact]
public void A()
{
Assert.NotNull(AssemblyFixture);
Assert.Equal(0, AssemblyFixture.Count);
Assert.NotNull(CollectionFixture);
Assert.Equal(0, CollectionFixture.Count);
AssemblyFixture.Count++;
CollectionFixture.Count++;
}
[Fact]
public void B()
{
Assert.Equal(1, AssemblyFixture.Count);
Assert.Equal(1, CollectionFixture.Count);
}
}
[CollectionDefinition("MyCollection", DisableParallelization = true)]
public class MyCollection : ICollectionFixture<TestCollectionFixture>
{
}
}

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Testing
Assert.True(false, "This test should always be skipped.");
}
#if NETCOREAPP3_0
#if NETCOREAPP
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public void ThisTestMustRunOnCoreCLR()

View File

@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Testing
Assert.True(true);
}
#if NETCOREAPP3_0
#if NETCOREAPP
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
[MemberData(nameof(GetInts))]

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
<!-- allow skipped tests -->
<NoWarn>$(NoWarn);xUnit1004</NoWarn>

View File

@ -0,0 +1,6 @@
using Microsoft.AspNetCore.Testing;
using Xunit;
[assembly: Repeat(1)]
[assembly: AssemblyFixture(typeof(TestAssemblyFixture))]
[assembly: TestFramework("Microsoft.AspNetCore.Testing.AspNetTestFramework", "Microsoft.AspNetCore.Testing")]

View File

@ -0,0 +1,43 @@
// 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.AspNetCore.Testing
{
[Repeat]
public class RepeatTest
{
public static int _runCount = 0;
[Fact]
[Repeat(5)]
public void RepeatLimitIsSetCorrectly()
{
Assert.Equal(5, RepeatContext.Current.Limit);
}
[Fact]
[Repeat(5)]
public void RepeatRunsTestSpecifiedNumberOfTimes()
{
Assert.Equal(RepeatContext.Current.CurrentIteration, _runCount);
_runCount++;
}
[Fact]
public void RepeatCanBeSetOnClass()
{
Assert.Equal(10, RepeatContext.Current.Limit);
}
}
public class LoggedTestXunitRepeatAssemblyTests
{
[Fact]
public void RepeatCanBeSetOnAssembly()
{
Assert.Equal(1, RepeatContext.Current.Limit);
}
}
}

View File

@ -0,0 +1,10 @@
// 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.AspNetCore.Testing
{
public class TestAssemblyFixture
{
public int Count { get; set; }
}
}

View File

@ -0,0 +1,10 @@
// 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.AspNetCore.Testing
{
public class TestCollectionFixture
{
public int Count { get; set; }
}
}

View File

@ -0,0 +1,83 @@
// 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;
namespace Microsoft.AspNetCore.Testing
{
public class TestContextTest : ITestMethodLifecycle
{
public TestContext Context { get; private set; }
[Fact]
public void FullName_IsUsed_ByDefault()
{
Assert.Equal(GetType().FullName, Context.FileOutput.TestClassName);
}
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
{
Context = context;
return Task.CompletedTask;
}
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
namespace Microsoft.AspNetCore.Testing.Tests
{
public class TestContextNameShorteningTest : ITestMethodLifecycle
{
public TestContext Context { get; private set; }
[Fact]
public void NameIsShortenedWhenAssemblyNameIsAPrefix()
{
Assert.Equal(GetType().Name, Context.FileOutput.TestClassName);
}
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
{
Context = context;
return Task.CompletedTask;
}
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
namespace Microsoft.AspNetCore.Testing
{
[ShortClassName]
public class TestContextTestClassShortNameAttributeTest : ITestMethodLifecycle
{
public TestContext Context { get; private set; }
[Fact]
public void ShortClassNameUsedWhenShortClassNameAttributeSpecified()
{
Assert.Equal(GetType().Name, Context.FileOutput.TestClassName);
}
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
{
Context = context;
return Task.CompletedTask;
}
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -1,7 +1,7 @@
<!-- This file is automatically generated. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="Microsoft.Extensions.WebEncoders.netstandard2.0.cs" />
@ -9,8 +9,8 @@
<Reference Include="Microsoft.Extensions.Options" />
<Reference Include="System.Text.Encodings.Web" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.Extensions.WebEncoders.netcoreapp3.0.cs" />
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.Extensions.WebEncoders.netcoreapp.cs" />
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>

View File

@ -2,8 +2,8 @@
<PropertyGroup>
<Description>Contains registration and configuration APIs to add the core framework encoders to a dependency injection container.</Description>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(DotNetBuildFromSource)' == 'true'">$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore</PackageTags>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>