Merge branch 'release/2.1' into release/2.2

\n\nCommit migrated from 18fcffbd25
This commit is contained in:
Nate McMaster 2018-10-30 16:39:06 -07:00
parent 5460fd093e
commit 45b0b83997
64 changed files with 3517 additions and 268 deletions

View File

@ -42,8 +42,7 @@ namespace Microsoft.Extensions.ObjectPool
public override T Get()
{
T item = _firstItem;
var item = _firstItem;
if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item)
{
item = GetViaScan();
@ -55,12 +54,10 @@ namespace Microsoft.Extensions.ObjectPool
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T GetViaScan()
{
ObjectWrapper[] items = _items;
var items = _items;
for (var i = 0; i < items.Length; i++)
{
T item = items[i];
var item = items[i].Element;
if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item)
{
return item;
@ -88,21 +85,17 @@ namespace Microsoft.Extensions.ObjectPool
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReturnViaScan(T obj)
{
ObjectWrapper[] items = _items;
var items = _items;
for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i)
{
}
}
// PERF: the struct wrapper avoids array-covariance-checks from the runtime when assigning to elements of the array.
[DebuggerDisplay("{Element}")]
private struct ObjectWrapper
{
public T Element;
public ObjectWrapper(T item) => Element = item;
public static implicit operator T(ObjectWrapper wrapper) => wrapper.Element;
}
}
}

View File

@ -1,12 +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.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Extensions.ObjectPool.Test
namespace Microsoft.Extensions.ObjectPool
{
public class DefaultObjectPoolTest
{

View File

@ -0,0 +1,80 @@
// 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;
using Xunit;
namespace Microsoft.Extensions.ObjectPool
{
public class ThreadingTest
{
private CancellationTokenSource _cts;
private DefaultObjectPool<Item> _pool;
private bool _foundError;
[Fact]
public void RunThreadingTest()
{
_cts = new CancellationTokenSource();
_pool = new DefaultObjectPool<Item>(new DefaultPooledObjectPolicy<Item>(), 10);
var threads = new Thread[8];
for (var i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(Run);
}
for (var i = 0; i < threads.Length; i++)
{
threads[i].Start();
}
// Run for 1000ms
_cts.CancelAfter(1000);
// Wait for all threads to complete
for (var i = 0; i < threads.Length; i++)
{
threads[i].Join();
}
Assert.False(_foundError, "Race condition found. An item was shared across threads.");
}
private void Run()
{
while (!_cts.IsCancellationRequested)
{
var obj = _pool.Get();
if (obj.i != 0)
{
_foundError = true;
}
obj.i = 123;
var obj2 = _pool.Get();
if (obj2.i != 0)
{
_foundError = true;
}
obj2.i = 321;
obj.Reset();
_pool.Return(obj);
obj2.Reset();
_pool.Return(obj2);
}
}
private class Item
{
public int i = 0;
public void Reset()
{
i = 0;
}
}
}
}

View File

@ -2,38 +2,72 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Generic;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess;
namespace BenchmarkDotNet.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)]
internal class AspNetCoreBenchmarkAttribute : Attribute, IConfigSource
{
public static bool UseValidationConfig { get; set; }
public Type ConfigType { get; }
public Type ValidationConfigType { get; }
public AspNetCoreBenchmarkAttribute() : this(typeof(DefaultCoreConfig))
public AspNetCoreBenchmarkAttribute()
: this(typeof(DefaultCoreConfig))
{
}
public AspNetCoreBenchmarkAttribute(Type configType) : this(configType, typeof(DefaultCoreValidationConfig))
public AspNetCoreBenchmarkAttribute(Type configType)
: this(configType, typeof(DefaultCoreValidationConfig))
{
}
public AspNetCoreBenchmarkAttribute(Type configType, Type validationConfigType)
{
ConfigType = configType;
ValidationConfigType = validationConfigType;
ConfigTypes = new Dictionary<string, Type>()
{
{ NamedConfiguration.Default, typeof(DefaultCoreConfig) },
{ NamedConfiguration.Validation, typeof(DefaultCoreValidationConfig) },
{ NamedConfiguration.Profile, typeof(DefaultCoreProfileConfig) },
{ NamedConfiguration.Debug, typeof(DefaultCoreDebugConfig) },
{ NamedConfiguration.PerfLab, typeof(DefaultCorePerfLabConfig) },
};
if (configType != null)
{
ConfigTypes[NamedConfiguration.Default] = configType;
}
if (validationConfigType != null)
{
ConfigTypes[NamedConfiguration.Validation] = validationConfigType;
}
}
public IConfig Config => (IConfig) Activator.CreateInstance(UseValidationConfig ? ValidationConfigType : ConfigType, Array.Empty<object>());
public IConfig Config
{
get
{
if (!ConfigTypes.TryGetValue(ConfigName ?? NamedConfiguration.Default, out var configType))
{
var message = $"Could not find a configuration matching {ConfigName}. " +
$"Known configurations: {string.Join(", ", ConfigTypes.Keys)}";
throw new InvalidOperationException(message);
}
return (IConfig)Activator.CreateInstance(configType, Array.Empty<object>());
}
}
public Dictionary<string, Type> ConfigTypes { get; }
public static string ConfigName { get; set; } = NamedConfiguration.Default;
public static class NamedConfiguration
{
public static readonly string Default = "default";
public static readonly string Validation = "validation";
public static readonly string Profile = "profile";
public static readonly string Debug = "debug";
public static readonly string PerfLab = "perflab";
}
}
}

View File

@ -28,7 +28,11 @@ namespace BenchmarkDotNet.Attributes
Add(JitOptimizationsValidator.FailOnError);
Add(Job.Core
#if NETCOREAPP2_1
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
#else
.With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp2.2", null, ".NET Core 2.2")))
#endif
.With(new GcMode { Server = true })
.With(RunStrategy.Throughput));
}

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 BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Validators;
namespace BenchmarkDotNet.Attributes
{
internal class DefaultCoreDebugConfig : ManualConfig
{
public DefaultCoreDebugConfig()
{
Add(ConsoleLogger.Default);
Add(JitOptimizationsValidator.DontFailOnError);
Add(Job.InProcess
.With(RunStrategy.Throughput));
}
}
}

View File

@ -0,0 +1,48 @@
// 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 BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Validators;
namespace BenchmarkDotNet.Attributes
{
internal class DefaultCorePerfLabConfig : ManualConfig
{
public DefaultCorePerfLabConfig()
{
Add(ConsoleLogger.Default);
Add(MemoryDiagnoser.Default);
Add(StatisticColumn.OperationsPerSecond);
Add(new ParamsSummaryColumn());
Add(DefaultColumnProviders.Statistics, DefaultColumnProviders.Diagnosers, DefaultColumnProviders.Target);
// TODO: When upgrading to BDN 0.11.1, use Add(DefaultColumnProviders.Descriptor);
// DefaultColumnProviders.Target is deprecated
Add(JitOptimizationsValidator.FailOnError);
Add(Job.InProcess
.With(RunStrategy.Throughput));
Add(MarkdownExporter.GitHub);
Add(new CsvExporter(
CsvSeparator.Comma,
new Reports.SummaryStyle
{
PrintUnitsInHeader = true,
PrintUnitsInContent = false,
TimeUnit = Horology.TimeUnit.Microsecond,
SizeUnit = SizeUnit.KB
}));
}
}
}

View File

@ -0,0 +1,32 @@
// 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 BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Validators;
namespace BenchmarkDotNet.Attributes
{
internal class DefaultCoreProfileConfig : ManualConfig
{
public DefaultCoreProfileConfig()
{
Add(ConsoleLogger.Default);
Add(MarkdownExporter.GitHub);
Add(MemoryDiagnoser.Default);
Add(StatisticColumn.OperationsPerSecond);
Add(DefaultColumnProviders.Instance);
Add(JitOptimizationsValidator.FailOnError);
Add(Job.InProcess
.With(RunStrategy.Throughput));
}
}
}

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 BenchmarkDotNet.Columns;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
namespace BenchmarkDotNet.Attributes
{
public class ParamsSummaryColumn : IColumn
{
public string Id => nameof(ParamsSummaryColumn);
public string ColumnName { get; } = "Params";
public bool IsDefault(Summary summary, Benchmark benchmark) => false;
public string GetValue(Summary summary, Benchmark benchmark) => benchmark.Parameters.DisplayInfo;
public bool IsAvailable(Summary summary) => true;
public bool AlwaysShow => true;
public ColumnCategory Category => ColumnCategory.Params;
public int PriorityInCategory => 0;
public override string ToString() => ColumnName;
public bool IsNumeric => false;
public UnitType UnitType => UnitType.Dimensionless;
public string GetValue(Summary summary, Benchmark benchmark, ISummaryStyle style) => GetValue(summary, benchmark);
public string Legend => $"Summary of all parameter values";
}
}

View File

@ -2,15 +2,14 @@
// 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.Linq;
using System.Reflection;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess;
using BenchmarkDotNet.Running;
namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner
{
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner
{
BeforeMain(args);
CheckValidate(ref args);
AssignConfiguration(ref args);
var summaries = BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, ManualConfig.CreateEmpty());
@ -66,16 +65,35 @@ namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner
return 1;
}
private static void CheckValidate(ref string[] args)
private static void AssignConfiguration(ref string[] args)
{
var argsList = args.ToList();
if (argsList.Remove("--validate") || argsList.Remove("--validate-fast"))
{
// Compat: support the old style of passing a config that is used by our build system.
SuppressConsole();
AspNetCoreBenchmarkAttribute.UseValidationConfig = true;
AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Validation;
args = argsList.ToArray();
return;
}
var index = argsList.IndexOf("--config");
if (index >= 0 && index < argsList.Count -1)
{
AspNetCoreBenchmarkAttribute.ConfigName = argsList[index + 1];
argsList.RemoveAt(index + 1);
argsList.RemoveAt(index);
args = argsList.ToArray();
return;
}
args = argsList.ToArray();
if (Debugger.IsAttached)
{
Console.WriteLine("Using the debug config since you are debugging. I hope that's OK!");
Console.WriteLine("Specify a configuration with --config <name> to override");
AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Debug;
return;
}
}
private static void SuppressConsole()

View File

@ -19,17 +19,12 @@ namespace Microsoft.AspNetCore.Certificates.Generation
public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1";
public const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate";
public const string AspNetIdentityOid = "1.3.6.1.4.1.311.84.1.2";
public const string AspNetIdentityOidFriendlyName = "ASP.NET Core Identity Json Web Token signing development certificate";
private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1";
private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication";
private const string LocalhostHttpsDnsName = "localhost";
private const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName;
private const string IdentityDistinguishedName = "CN=Microsoft.AspNetCore.Identity.Signing";
public const int RSAMinimumKeySizeInBits = 2048;
private static readonly TimeSpan MaxRegexTimeout = TimeSpan.FromMinutes(1);
@ -37,7 +32,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain";
private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db";
private const string MacOSFindCertificateCommandLine = "security";
#if NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2
private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain;
#endif
private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)";
@ -46,7 +41,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
private const string MacOSDeleteCertificateCommandLine = "sudo";
private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}";
private const string MacOSTrustCertificateCommandLine = "sudo";
#if NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " ";
#endif
private const int UserCancelledErrorCode = 1223;
@ -56,8 +51,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation
StoreName storeName,
StoreLocation location,
bool isValid,
bool requireExportable = true)
bool requireExportable = true,
DiagnosticInformation diagnostics = null)
{
diagnostics?.Debug($"Listing '{purpose.ToString()}' certificates on '{location}\\{storeName}'.");
var certificates = new List<X509Certificate2>();
try
{
@ -70,28 +67,37 @@ namespace Microsoft.AspNetCore.Certificates.Generation
{
case CertificatePurpose.All:
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid));
.Where(c => HasOid(c, AspNetHttpsOid));
break;
case CertificatePurpose.HTTPS:
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetHttpsOid));
break;
case CertificatePurpose.Signing:
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetIdentityOid));
break;
default:
break;
}
diagnostics?.Debug(diagnostics.DescribeCertificates(matchingCertificates));
if (isValid)
{
// Ensure the certificate hasn't expired, has a private key and its exportable
// (for container/unix scenarios).
diagnostics?.Debug("Checking certificates for validity.");
var now = DateTimeOffset.Now;
matchingCertificates = matchingCertificates
var validCertificates = matchingCertificates
.Where(c => c.NotBefore <= now &&
now <= c.NotAfter &&
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c)));
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c)))
.ToArray();
var invalidCertificates = matchingCertificates.Except(validCertificates);
diagnostics?.Debug("Listing valid certificates");
diagnostics?.Debug(diagnostics.DescribeCertificates(validCertificates));
diagnostics?.Debug("Listing invalid certificates");
diagnostics?.Debug(diagnostics.DescribeCertificates(invalidCertificates));
matchingCertificates = validCertificates;
}
// We need to enumerate the certificates early to prevent dispoisng issues.
@ -123,7 +129,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport));
#else
// Only check for RSA CryptoServiceProvider and do not fail in XPlat tooling as
// System.Security.Cryptography.Cng is not pat of the shared framework and we don't
// System.Security.Cryptography.Cng is not part of the shared framework and we don't
// want to bring the dependency in on CLI scenarios. This functionality will be used
// on CLI scenarios as part of the first run experience, so checking the exportability
// of the certificate is not important.
@ -133,7 +139,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
#endif
}
private void DisposeCertificates(IEnumerable<X509Certificate2> disposables)
private static void DisposeCertificates(IEnumerable<X509Certificate2> disposables)
{
foreach (var disposable in disposables)
{
@ -147,9 +153,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
}
#if NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2
public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride)
public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride, DiagnosticInformation diagnostics = null)
{
var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName);
var extensions = new List<X509Extension>();
@ -192,46 +198,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation
return certificate;
}
public X509Certificate2 CreateApplicationTokenSigningDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride)
{
var subject = new X500DistinguishedName(subjectOverride ?? IdentityDistinguishedName);
var extensions = new List<X509Extension>();
var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true);
var enhancedKeyUsage = new X509EnhancedKeyUsageExtension(
new OidCollection() {
new Oid(
ServerAuthenticationEnhancedKeyUsageOid,
ServerAuthenticationEnhancedKeyUsageOidFriendlyName)
},
critical: true);
var basicConstraints = new X509BasicConstraintsExtension(
certificateAuthority: false,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: true);
var aspNetIdentityExtension = new X509Extension(
new AsnEncodedData(
new Oid(AspNetIdentityOid, AspNetIdentityOidFriendlyName),
Encoding.ASCII.GetBytes(AspNetIdentityOidFriendlyName)),
critical: false);
extensions.Add(basicConstraints);
extensions.Add(keyUsage);
extensions.Add(enhancedKeyUsage);
extensions.Add(aspNetIdentityExtension);
var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
certificate.FriendlyName = AspNetIdentityOidFriendlyName;
}
return certificate;
}
public X509Certificate2 CreateSelfSignedCertificate(
X500DistinguishedName subject,
IEnumerable<X509Extension> extensions,
@ -260,8 +226,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
}
public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location)
public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location, DiagnosticInformation diagnostics = null)
{
diagnostics?.Debug("Saving the certificate into the certificate store.");
var imported = certificate;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
@ -287,33 +254,67 @@ namespace Microsoft.AspNetCore.Certificates.Generation
return imported;
}
public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password)
public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password, DiagnosticInformation diagnostics = null)
{
if (Path.GetDirectoryName(path) != "")
diagnostics?.Debug(
$"Exporting certificate to '{path}'",
includePrivateKey ? "The certificate will contain the private key" : "The certificate will not contain the private key");
if (includePrivateKey && password == null)
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
diagnostics?.Debug("No password was provided for the certificate.");
}
var targetDirectoryPath = Path.GetDirectoryName(path);
if (targetDirectoryPath != "")
{
diagnostics?.Debug($"Ensuring that the directory for the target exported certificate path exists '{targetDirectoryPath}'");
Directory.CreateDirectory(targetDirectoryPath);
}
byte[] bytes;
if (includePrivateKey)
{
var bytes = certificate.Export(X509ContentType.Pkcs12, password);
try
{
File.WriteAllBytes(path, bytes);
diagnostics?.Debug($"Exporting the certificate including the private key.");
bytes = certificate.Export(X509ContentType.Pkcs12, password);
}
finally
catch (Exception e)
{
Array.Clear(bytes, 0, bytes.Length);
diagnostics?.Error($"Failed to export the certificate with the private key", e);
throw;
}
}
else
{
var bytes = certificate.Export(X509ContentType.Cert);
try
{
diagnostics?.Debug($"Exporting the certificate without the private key.");
bytes = certificate.Export(X509ContentType.Cert);
}
catch (Exception ex)
{
diagnostics?.Error($"Failed to export the certificate without the private key", ex);
throw;
}
}
try
{
diagnostics?.Debug($"Writing exported certificate to path '{path}'.");
File.WriteAllBytes(path, bytes);
}
catch (Exception ex)
{
diagnostics?.Error("Failed writing the certificate to the target path", ex);
throw;
}
finally
{
Array.Clear(bytes, 0, bytes.Length);
}
}
public void TrustCertificate(X509Certificate2 certificate)
public void TrustCertificate(X509Certificate2 certificate, DiagnosticInformation diagnostics = null)
{
// Strip certificate of the private key if any.
var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert));
@ -322,21 +323,24 @@ namespace Microsoft.AspNetCore.Certificates.Generation
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
TrustCertificateOnWindows(certificate, publicCertificate);
diagnostics?.Debug("Trusting the certificate on Windows.");
TrustCertificateOnWindows(certificate, publicCertificate, diagnostics);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
TrustCertificateOnMac(publicCertificate);
diagnostics?.Debug("Trusting the certificate on MAC.");
TrustCertificateOnMac(publicCertificate, diagnostics);
}
}
}
private void TrustCertificateOnMac(X509Certificate2 publicCertificate)
private void TrustCertificateOnMac(X509Certificate2 publicCertificate, DiagnosticInformation diagnostics)
{
var tmpFile = Path.GetTempFileName();
try
{
ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null);
diagnostics?.Debug("Running the trust command on Mac OS");
using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile))
{
process.WaitForExit();
@ -362,19 +366,29 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
}
private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate)
private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate, DiagnosticInformation diagnostics = null)
{
publicCertificate.FriendlyName = certificate.FriendlyName;
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var existing = store.Certificates.Find(X509FindType.FindByThumbprint, publicCertificate.Thumbprint, validOnly: false);
if (existing.Count > 0)
{
diagnostics?.Debug("Certificate already trusted. Skipping trust step.");
DisposeCertificates(existing.OfType<X509Certificate2>());
return;
}
try
{
diagnostics?.Debug("Adding certificate to the store.");
store.Add(publicCertificate);
}
catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode)
{
diagnostics?.Debug("User cancelled the trust prompt.");
throw new UserCancelledTrustException();
}
store.Close();
@ -437,6 +451,30 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
}
public DiagnosticInformation CleanupHttpsCertificates2(string subject = LocalhostHttpsDistinguishedName)
{
return CleanupCertificates2(CertificatePurpose.HTTPS, subject);
}
public DiagnosticInformation CleanupCertificates2(CertificatePurpose purpose, string subject)
{
var diagnostics = new DiagnosticInformation();
// On OS X we don't have a good way to manage trusted certificates in the system keychain
// so we do everything by invoking the native toolchain.
// This has some limitations, like for example not being able to identify our custom OID extension. For that
// matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates.
// To do this, we list the certificates that we can identify on the current user personal store and we invoke
// the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates,
// we remove the certificates from the local user store to finish up the cleanup.
var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false, requireExportable: true, diagnostics);
foreach (var certificate in certificates)
{
RemoveCertificate(certificate, RemoveLocations.All, diagnostics);
}
return diagnostics;
}
public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null)
{
var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
@ -454,32 +492,33 @@ namespace Microsoft.AspNetCore.Certificates.Generation
DisposeCertificates(certificates);
}
private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations)
private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations, DiagnosticInformation diagnostics = null)
{
switch (locations)
{
case RemoveLocations.Undefined:
throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location.");
case RemoveLocations.Local:
RemoveCertificateFromUserStore(certificate);
RemoveCertificateFromUserStore(certificate, diagnostics);
break;
case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux):
RemoveCertificateFromTrustedRoots(certificate);
RemoveCertificateFromTrustedRoots(certificate, diagnostics);
break;
case RemoveLocations.All:
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
RemoveCertificateFromTrustedRoots(certificate);
RemoveCertificateFromTrustedRoots(certificate, diagnostics);
}
RemoveCertificateFromUserStore(certificate);
RemoveCertificateFromUserStore(certificate, diagnostics);
break;
default:
throw new InvalidOperationException("Invalid location.");
}
}
private static void RemoveCertificateFromUserStore(X509Certificate2 certificate)
private static void RemoveCertificateFromUserStore(X509Certificate2 certificate, DiagnosticInformation diagnostics)
{
diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'.");
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
@ -492,8 +531,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
}
private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate)
private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate, DiagnosticInformation diagnostics)
{
diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.Root}'.");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
@ -501,9 +541,13 @@ namespace Microsoft.AspNetCore.Certificates.Generation
store.Open(OpenFlags.ReadWrite);
var matching = store.Certificates
.OfType<X509Certificate2>()
.Single(c => c.SerialNumber == certificate.SerialNumber);
.SingleOrDefault(c => c.SerialNumber == certificate.SerialNumber);
if (matching != null)
{
store.Remove(matching);
}
store.Remove(matching);
store.Close();
}
}
@ -513,10 +557,12 @@ namespace Microsoft.AspNetCore.Certificates.Generation
{
try
{
diagnostics?.Debug("Trying to remove the certificate trust rule.");
RemoveCertificateTrustRule(certificate);
}
catch
{
diagnostics?.Debug("Failed to remove the certificate trust rule.");
// We don't care if we fail to remove the trust rule if
// for some reason the certificate became untrusted.
// The delete command will fail if the certificate is
@ -524,6 +570,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation
}
RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate);
}
else
{
diagnostics?.Debug("The certificate was not trusted.");
}
}
}
@ -601,18 +651,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation
return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject);
}
public EnsureCertificateResult EnsureAspNetCoreApplicationTokensDevelopmentCertificate(
DateTimeOffset notBefore,
DateTimeOffset notAfter,
string path = null,
bool trust = false,
bool includePrivateKey = false,
string password = null,
string subject = IdentityDistinguishedName)
{
return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.Signing, path, trust, includePrivateKey, password, subject);
}
public EnsureCertificateResult EnsureValidCertificateExists(
DateTimeOffset notBefore,
DateTimeOffset notAfter,
@ -652,9 +690,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation
case CertificatePurpose.HTTPS:
certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride);
break;
case CertificatePurpose.Signing:
certificate = CreateApplicationTokenSigningDevelopmentCertificate(notBefore, notAfter, subjectOverride);
break;
default:
throw new InvalidOperationException("The certificate must have a purpose.");
}
@ -704,6 +739,143 @@ namespace Microsoft.AspNetCore.Certificates.Generation
return result;
}
// This is just to avoid breaking changes across repos.
// Will be renamed back to EnsureAspNetCoreHttpsDevelopmentCertificate once updates are made elsewhere.
public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate2(
DateTimeOffset notBefore,
DateTimeOffset notAfter,
string path = null,
bool trust = false,
bool includePrivateKey = false,
string password = null,
string subject = LocalhostHttpsDistinguishedName)
{
return EnsureValidCertificateExists2(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject);
}
public DetailedEnsureCertificateResult EnsureValidCertificateExists2(
DateTimeOffset notBefore,
DateTimeOffset notAfter,
CertificatePurpose purpose,
string path,
bool trust,
bool includePrivateKey,
string password,
string subject)
{
if (purpose == CertificatePurpose.All)
{
throw new ArgumentException("The certificate must have a specific purpose.");
}
var result = new DetailedEnsureCertificateResult();
var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: true, result.Diagnostics).Concat(
ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true, requireExportable: true, result.Diagnostics));
var filteredCertificates = subject == null ? certificates : certificates.Where(c => c.Subject == subject);
if (subject != null)
{
var excludedCertificates = certificates.Except(filteredCertificates);
result.Diagnostics.Debug($"Filtering found certificates to those with a subject equal to '{subject}'");
result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(filteredCertificates));
result.Diagnostics.Debug($"Listing certificates excluded from consideration.");
result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(excludedCertificates));
}
else
{
result.Diagnostics.Debug("Skipped filtering certificates by subject.");
}
certificates = filteredCertificates;
result.ResultCode = EnsureCertificateResult.Succeeded;
X509Certificate2 certificate = null;
if (certificates.Count() > 0)
{
result.Diagnostics.Debug("Found valid certificates present on the machine.");
result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificates));
certificate = certificates.First();
result.Diagnostics.Debug("Selected certificate");
result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate));
result.ResultCode = EnsureCertificateResult.ValidCertificatePresent;
}
else
{
result.Diagnostics.Debug("No valid certificates present on this machine. Trying to create one.");
try
{
switch (purpose)
{
case CertificatePurpose.All:
throw new InvalidOperationException("The certificate must have a specific purpose.");
case CertificatePurpose.HTTPS:
certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subject, result.Diagnostics);
break;
default:
throw new InvalidOperationException("The certificate must have a purpose.");
}
}
catch (Exception e)
{
result.Diagnostics.Error("Error creating the certificate.", e);
result.ResultCode = EnsureCertificateResult.ErrorCreatingTheCertificate;
return result;
}
try
{
certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser, result.Diagnostics);
}
catch (Exception e)
{
result.Diagnostics.Error($"Error saving the certificate in the certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'.", e);
result.ResultCode = EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
return result;
}
}
if (path != null)
{
result.Diagnostics.Debug("Trying to export the certificate.");
result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate));
try
{
ExportCertificate(certificate, path, includePrivateKey, password, result.Diagnostics);
}
catch (Exception e)
{
result.Diagnostics.Error("An error ocurred exporting the certificate.", e);
result.ResultCode = EnsureCertificateResult.ErrorExportingTheCertificate;
return result;
}
}
if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust)
{
try
{
result.Diagnostics.Debug("Trying to export the certificate.");
TrustCertificate(certificate, result.Diagnostics);
}
catch (UserCancelledTrustException)
{
result.Diagnostics.Error("The user cancelled trusting the certificate.", null);
result.ResultCode = EnsureCertificateResult.UserCancelledTrustStep;
return result;
}
catch (Exception e)
{
result.Diagnostics.Error("There was an error trusting the certificate.", e);
result.ResultCode = EnsureCertificateResult.FailedToTrustTheCertificate;
return result;
}
}
return result;
}
private class UserCancelledTrustException : Exception
{
}
@ -715,6 +887,65 @@ namespace Microsoft.AspNetCore.Certificates.Generation
Trusted,
All
}
internal class DetailedEnsureCertificateResult
{
public EnsureCertificateResult ResultCode { get; set; }
public DiagnosticInformation Diagnostics { get; set; } = new DiagnosticInformation();
}
#endif
internal class DiagnosticInformation
{
public IList<string> Messages { get; } = new List<string>();
public IList<Exception> Exceptions { get; } = new List<Exception>();
internal void Debug(params string[] messages)
{
foreach (var message in messages)
{
Messages.Add(message);
}
}
internal string[] DescribeCertificates(params X509Certificate2[] certificates)
{
return DescribeCertificates(certificates.AsEnumerable());
}
internal string[] DescribeCertificates(IEnumerable<X509Certificate2> certificates)
{
var result = new List<string>();
result.Add($"'{certificates.Count()}' found matching the criteria.");
result.Add($"SUBJECT - THUMBPRINT - NOT BEFORE - EXPIRES - HAS PRIVATE KEY");
foreach (var certificate in certificates)
{
result.Add(DescribeCertificate(certificate));
}
return result.ToArray();
}
private static string DescribeCertificate(X509Certificate2 certificate) =>
$"{certificate.Subject} - {certificate.Thumbprint} - {certificate.NotBefore} - {certificate.NotAfter} - {certificate.HasPrivateKey}";
internal void Error(string preamble, Exception e)
{
Messages.Add(preamble);
if (Exceptions.Count > 0 && Exceptions[Exceptions.Count - 1] == e)
{
return;
}
var ex = e;
while (ex != null)
{
Messages.Add("Exception message: " + ex.Message);
ex = ex.InnerException;
}
}
}
}
}

View File

@ -6,7 +6,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation
internal enum CertificatePurpose
{
All,
HTTPS,
Signing
HTTPS
}
}

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 NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2
namespace Microsoft.AspNetCore.Certificates.Generation
{

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 System;
using System.Threading;
namespace Microsoft.Extensions.Internal
{
// A convenience API for interacting with System.Threading.Timer in a way
// that doesn't capture the ExecutionContext. We should be using this (or equivalent)
// everywhere we use timers to avoid rooting any values stored in asynclocals.
internal static class NonCapturingTimer
{
public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer
bool restoreFlow = false;
try
{
if (!ExecutionContext.IsFlowSuppressed())
{
ExecutionContext.SuppressFlow();
restoreFlow = true;
}
return new Timer(callback, state, dueTime, period);
}
finally
{
// Restore the current ExecutionContext
if (restoreFlow)
{
ExecutionContext.RestoreFlow();
}
}
}
}
}

View File

@ -14,44 +14,40 @@ namespace Microsoft.Extensions.Internal
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
public static void KillTree(this Process process)
{
process.KillTree(_defaultTimeout);
}
public static void KillTree(this Process process) => process.KillTree(_defaultTimeout);
public static void KillTree(this Process process, TimeSpan timeout)
{
string stdout;
var pid = process.Id;
if (_isWindows)
{
RunProcessAndWaitForExit(
"taskkill",
$"/T /F /PID {process.Id}",
$"/T /F /PID {pid}",
timeout,
out stdout);
out var _);
}
else
{
var children = new HashSet<int>();
GetAllChildIdsUnix(process.Id, children, timeout);
GetAllChildIdsUnix(pid, children, timeout);
foreach (var childId in children)
{
KillProcessUnix(childId, timeout);
}
KillProcessUnix(process.Id, timeout);
KillProcessUnix(pid, timeout);
}
}
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{
string stdout;
var exitCode = RunProcessAndWaitForExit(
RunProcessAndWaitForExit(
"pgrep",
$"-P {parentId}",
timeout,
out stdout);
out var stdout);
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
if (!string.IsNullOrEmpty(stdout))
{
using (var reader = new StringReader(stdout))
{
@ -63,8 +59,7 @@ namespace Microsoft.Extensions.Internal
return;
}
int id;
if (int.TryParse(text, out id))
if (int.TryParse(text, out var id))
{
children.Add(id);
// Recursively get the children
@ -77,22 +72,22 @@ namespace Microsoft.Extensions.Internal
private static void KillProcessUnix(int processId, TimeSpan timeout)
{
string stdout;
RunProcessAndWaitForExit(
"kill",
$"-TERM {processId}",
timeout,
out stdout);
out var stdout);
}
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
private static void RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
{
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false
RedirectStandardError = true,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
@ -106,8 +101,6 @@ namespace Microsoft.Extensions.Internal
{
process.Kill();
}
return process.ExitCode;
}
}
}

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal
{
@ -37,6 +38,12 @@ namespace Microsoft.Extensions.Internal
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> VisiblePropertiesCache =
new ConcurrentDictionary<Type, PropertyHelper[]>();
// We need to be able to check if a type is a 'ref struct' - but we need to be able to compile
// for platforms where the attribute is not defined, like net46. So we can fetch the attribute
// by late binding. If the attribute isn't defined, then we assume we won't encounter any
// 'ref struct' types.
private static readonly Type IsByRefLikeAttribute = Type.GetType("System.Runtime.CompilerServices.IsByRefLikeAttribute", throwOnError: false);
private Action<object, object> _valueSetter;
private Func<object, object> _valueGetter;
@ -511,16 +518,34 @@ namespace Microsoft.Extensions.Internal
return helpers;
}
// Indexed properties are not useful (or valid) for grabbing properties off an object.
private static bool IsInterestingProperty(PropertyInfo property)
{
// For improving application startup time, do not use GetIndexParameters() api early in this check as it
// creates a copy of parameter array and also we would like to check for the presence of a get method
// and short circuit asap.
return property.GetMethod != null &&
return
property.GetMethod != null &&
property.GetMethod.IsPublic &&
!property.GetMethod.IsStatic &&
// PropertyHelper can't work with ref structs.
!IsRefStructProperty(property) &&
// Indexed properties are not useful (or valid) for grabbing properties off an object.
property.GetMethod.GetParameters().Length == 0;
}
// PropertyHelper can't really interact with ref-struct properties since they can't be
// boxed and can't be used as generic types. We just ignore them.
//
// see: https://github.com/aspnet/Mvc/issues/8545
private static bool IsRefStructProperty(PropertyInfo property)
{
return
IsByRefLikeAttribute != null &&
property.PropertyType.IsValueType &&
property.PropertyType.IsDefined(IsByRefLikeAttribute);
}
}
}

View File

@ -1,7 +1,7 @@
// 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.
#if NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_2
using System;
using System.IO;
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
public ITestOutputHelper Output { get; }
[Fact]
[Fact(Skip = "True")]
public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates()
{
try
@ -109,6 +109,92 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
}
}
[Fact]
public void EnsureCreateHttpsCertificate2_CreatesACertificate_WhenThereAreNoHttpsCertificates()
{
try
{
// Arrange
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer";
var manager = new CertificateManager();
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
// Act
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate2(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject);
// Assert
Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode);
Assert.NotNull(result.Diagnostics);
Assert.NotEmpty(result.Diagnostics.Messages);
Assert.Empty(result.Diagnostics.Exceptions);
Assert.True(File.Exists(CertificateName));
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName));
Assert.NotNull(exportedCertificate);
Assert.False(exportedCertificate.HasPrivateKey);
var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
Assert.True(httpsCertificate.HasPrivateKey);
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer);
Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName);
Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value);
Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore);
Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter);
Assert.Contains(
httpsCertificate.Extensions.OfType<X509Extension>(),
e => e is X509BasicConstraintsExtension basicConstraints &&
basicConstraints.Critical == true &&
basicConstraints.CertificateAuthority == false &&
basicConstraints.HasPathLengthConstraint == false &&
basicConstraints.PathLengthConstraint == 0);
Assert.Contains(
httpsCertificate.Extensions.OfType<X509Extension>(),
e => e is X509KeyUsageExtension keyUsage &&
keyUsage.Critical == true &&
keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment);
Assert.Contains(
httpsCertificate.Extensions.OfType<X509Extension>(),
e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage &&
enhancedKeyUsage.Critical == true &&
enhancedKeyUsage.EnhancedKeyUsages.OfType<Oid>().Single() is Oid keyUsage &&
keyUsage.Value == "1.3.6.1.5.5.7.3.1");
// Subject alternative name
Assert.Contains(
httpsCertificate.Extensions.OfType<X509Extension>(),
e => e.Critical == true &&
e.Oid.Value == "2.5.29.17");
// ASP.NET HTTPS Development certificate extension
Assert.Contains(
httpsCertificate.Extensions.OfType<X509Extension>(),
e => e.Critical == false &&
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" &&
Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate");
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
}
catch (Exception e)
{
Output.WriteLine(e.Message);
ListCertificates(Output);
throw;
}
}
private void ListCertificates(ITestOutputHelper output)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
@ -125,7 +211,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
}
}
[Fact]
[Fact(Skip = "true")]
public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates()
{
// Arrange
@ -196,108 +282,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
}
}
[Fact]
public void EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates()
{
// Arrange
const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer";
var manager = new CertificateManager();
manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
// Act
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject);
// Assert
Assert.Equal(EnsureCertificateResult.Succeeded, result);
Assert.True(File.Exists(CertificateName));
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName));
Assert.NotNull(exportedCertificate);
Assert.False(exportedCertificate.HasPrivateKey);
var identityCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false);
var identityCertificate = Assert.Single(identityCertificates, i => i.Subject == TestCertificateSubject);
Assert.True(identityCertificate.HasPrivateKey);
Assert.Equal(TestCertificateSubject, identityCertificate.Subject);
Assert.Equal(TestCertificateSubject, identityCertificate.Issuer);
Assert.Equal("sha256RSA", identityCertificate.SignatureAlgorithm.FriendlyName);
Assert.Equal("1.2.840.113549.1.1.11", identityCertificate.SignatureAlgorithm.Value);
Assert.Equal(now.LocalDateTime, identityCertificate.NotBefore);
Assert.Equal(now.AddYears(1).LocalDateTime, identityCertificate.NotAfter);
Assert.Contains(
identityCertificate.Extensions.OfType<X509Extension>(),
e => e is X509BasicConstraintsExtension basicConstraints &&
basicConstraints.Critical == true &&
basicConstraints.CertificateAuthority == false &&
basicConstraints.HasPathLengthConstraint == false &&
basicConstraints.PathLengthConstraint == 0);
Assert.Contains(
identityCertificate.Extensions.OfType<X509Extension>(),
e => e is X509KeyUsageExtension keyUsage &&
keyUsage.Critical == true &&
keyUsage.KeyUsages == X509KeyUsageFlags.DigitalSignature);
Assert.Contains(
identityCertificate.Extensions.OfType<X509Extension>(),
e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage &&
enhancedKeyUsage.Critical == true &&
enhancedKeyUsage.EnhancedKeyUsages.OfType<Oid>().Single() is Oid keyUsage &&
keyUsage.Value == "1.3.6.1.5.5.7.3.1");
// ASP.NET Core Identity Json Web Token signing development certificate
Assert.Contains(
identityCertificate.Extensions.OfType<X509Extension>(),
e => e.Critical == false &&
e.Oid.Value == "1.3.6.1.4.1.311.84.1.2" &&
Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core Identity Json Web Token signing development certificate");
Assert.Equal(identityCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
}
[Fact]
public void EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates()
{
// Arrange
const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx";
var certificatePassword = Guid.NewGuid().ToString();
var manager = new CertificateManager();
manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
var identityTokenSigningCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
// Act
var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject);
// Assert
Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result);
Assert.True(File.Exists(CertificateName));
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword);
Assert.NotNull(exportedCertificate);
Assert.True(exportedCertificate.HasPrivateKey);
Assert.Equal(identityTokenSigningCertificates.GetCertHashString(), exportedCertificate.GetCertHashString());
}
}
}

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 NETCOREAPP2_0 || NETCOREAPP2_1
#if NETCOREAPP2_2
using System.IO;
using System.Runtime.InteropServices;
using Xunit;

View File

@ -0,0 +1,40 @@
// 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.Extensions.Internal
{
public class NonCapturingTimerTest
{
[Fact]
public async Task NonCapturingTimer_DoesntCaptureExecutionContext()
{
// Arrange
var message = new AsyncLocal<string>();
message.Value = "Hey, this is a value stored in the execuion context";
var tcs = new TaskCompletionSource<string>();
// Act
var timer = NonCapturingTimer.Create((_) =>
{
// Observe the value based on the current execution context
tcs.SetResult(message.Value);
}, state: null, dueTime: TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan);
// Assert
var messageFromTimer = await tcs.Task;
timer.Dispose();
// ExecutionContext didn't flow to timer callback
Assert.Null(messageFromTimer);
// ExecutionContext was restored
Assert.NotNull(await Task.Run(() => message.Value));
}
}
}

View File

@ -153,6 +153,22 @@ namespace Microsoft.Extensions.Internal
Assert.Equal("Prop5", helper.Name);
}
#if NETSTANDARD || NETCOREAPP
[Fact]
public void PropertyHelper_RefStructProperties()
{
// Arrange
var obj = new RefStructProperties();
// Act + Assert
var helper = Assert.Single(PropertyHelper.GetProperties(obj.GetType().GetTypeInfo()));
Assert.Equal("Prop5", helper.Name);
}
#elif NET46 || NET461
#else
#error Unknown TFM - update the set of TFMs where we test for ref structs
#endif
[Fact]
public void PropertyHelper_DoesNotFindSetOnlyProperties()
{
@ -718,6 +734,22 @@ namespace Microsoft.Extensions.Internal
public int Prop5 { get; set; }
}
#if NETSTANDARD || NETCOREAPP
private class RefStructProperties
{
public Span<bool> Span => throw new NotImplementedException();
public MyRefStruct UserDefined => throw new NotImplementedException();
public int Prop5 { get; set; }
}
private readonly ref struct MyRefStruct
{
}
#elif NET46 || NET461
#else
#error Unknown TFM - update the set of TFMs where we test for ref structs
#endif
private struct MyProperties
{
public int IntProp { get; set; }

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DebugType>portable</DebugType>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,79 @@
// 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.Globalization;
using System.Threading;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class CultureReplacer : IDisposable
{
private const string _defaultCultureName = "en-GB";
private const string _defaultUICultureName = "en-US";
private static readonly CultureInfo _defaultCulture = new CultureInfo(_defaultCultureName);
private readonly CultureInfo _originalCulture;
private readonly CultureInfo _originalUICulture;
private readonly long _threadId;
// Culture => Formatting of dates/times/money/etc, defaults to en-GB because en-US is the same as InvariantCulture
// We want to be able to find issues where the InvariantCulture is used, but a specific culture should be.
//
// UICulture => Language
public CultureReplacer(string culture = _defaultCultureName, string uiCulture = _defaultUICultureName)
: this(new CultureInfo(culture), new CultureInfo(uiCulture))
{
}
public CultureReplacer(CultureInfo culture, CultureInfo uiCulture)
{
_originalCulture = CultureInfo.CurrentCulture;
_originalUICulture = CultureInfo.CurrentUICulture;
_threadId = Thread.CurrentThread.ManagedThreadId;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = uiCulture;
}
/// <summary>
/// The name of the culture that is used as the default value for CultureInfo.DefaultThreadCurrentCulture when CultureReplacer is used.
/// </summary>
public static string DefaultCultureName
{
get { return _defaultCultureName; }
}
/// <summary>
/// The name of the culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentUICulture when CultureReplacer is used.
/// </summary>
public static string DefaultUICultureName
{
get { return _defaultUICultureName; }
}
/// <summary>
/// The culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentCulture when CultureReplacer is used.
/// </summary>
public static CultureInfo DefaultCulture
{
get { return _defaultCulture; }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
Assert.True(Thread.CurrentThread.ManagedThreadId == _threadId,
"The current thread is not the same as the thread invoking the constructor. This should never happen.");
CultureInfo.CurrentCulture = _originalCulture;
CultureInfo.CurrentUICulture = _originalUICulture;
}
}
}
}

View File

@ -0,0 +1,271 @@
// 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 System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
// TODO: eventually want: public partial class Assert : Xunit.Assert
public static class ExceptionAssert
{
/// <summary>
/// Verifies that an exception of the given type (or optionally a derived type) is thrown.
/// </summary>
/// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <returns>The exception that was thrown, when successful</returns>
public static TException Throws<TException>(Action testCode)
where TException : Exception
{
return VerifyException<TException>(RecordException(testCode));
}
/// <summary>
/// Verifies that an exception of the given type is thrown.
/// Also verifies that the exception message matches.
/// </summary>
/// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <returns>The exception that was thrown, when successful</returns>
public static TException Throws<TException>(Action testCode, string exceptionMessage)
where TException : Exception
{
var ex = Throws<TException>(testCode);
VerifyExceptionMessage(ex, exceptionMessage);
return ex;
}
/// <summary>
/// Verifies that an exception of the given type is thrown.
/// Also verifies that the exception message matches.
/// </summary>
/// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <returns>The exception that was thrown, when successful</returns>
public static async Task<TException> ThrowsAsync<TException>(Func<Task> testCode, string exceptionMessage)
where TException : Exception
{
// The 'testCode' Task might execute asynchronously in a different thread making it hard to enforce the thread culture.
// The correct way to verify exception messages in such a scenario would be to run the task synchronously inside of a
// culture enforced block.
var ex = await Assert.ThrowsAsync<TException>(testCode);
VerifyExceptionMessage(ex, exceptionMessage);
return ex;
}
/// <summary>
/// Verifies that an exception of the given type is thrown.
/// Also verified that the exception message matches.
/// </summary>
/// <typeparam name="TException">The type of the exception expected to be thrown</typeparam>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <returns>The exception that was thrown, when successful</returns>
public static TException Throws<TException>(Func<object> testCode, string exceptionMessage)
where TException : Exception
{
return Throws<TException>(() => { testCode(); }, exceptionMessage);
}
/// <summary>
/// Verifies that the code throws an <see cref="ArgumentException"/>.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <returns>The exception that was thrown, when successful</returns>
public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage)
{
return ThrowsArgumentInternal<ArgumentException>(testCode, paramName, exceptionMessage);
}
private static TException ThrowsArgumentInternal<TException>(
Action testCode,
string paramName,
string exceptionMessage)
where TException : ArgumentException
{
var ex = Throws<TException>(testCode);
if (paramName != null)
{
Assert.Equal(paramName, ex.ParamName);
}
VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true);
return ex;
}
/// <summary>
/// Verifies that the code throws an <see cref="ArgumentException"/>.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <returns>The exception that was thrown, when successful</returns>
public static Task<ArgumentException> ThrowsArgumentAsync(Func<Task> testCode, string paramName, string exceptionMessage)
{
return ThrowsArgumentAsyncInternal<ArgumentException>(testCode, paramName, exceptionMessage);
}
private static async Task<TException> ThrowsArgumentAsyncInternal<TException>(
Func<Task> testCode,
string paramName,
string exceptionMessage)
where TException : ArgumentException
{
var ex = await Assert.ThrowsAsync<TException>(testCode);
if (paramName != null)
{
Assert.Equal(paramName, ex.ParamName);
}
VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true);
return ex;
}
/// <summary>
/// Verifies that the code throws an <see cref="ArgumentNullException"/>.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <returns>The exception that was thrown, when successful</returns>
public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName)
{
var ex = Throws<ArgumentNullException>(testCode);
if (paramName != null)
{
Assert.Equal(paramName, ex.ParamName);
}
return ex;
}
/// <summary>
/// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot
/// be null or empty.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <returns>The exception that was thrown, when successful</returns>
public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName)
{
return ThrowsArgumentInternal<ArgumentException>(testCode, paramName, "Value cannot be null or empty.");
}
/// <summary>
/// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot
/// be null or empty.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <returns>The exception that was thrown, when successful</returns>
public static Task<ArgumentException> ThrowsArgumentNullOrEmptyAsync(Func<Task> testCode, string paramName)
{
return ThrowsArgumentAsyncInternal<ArgumentException>(testCode, paramName, "Value cannot be null or empty.");
}
/// <summary>
/// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot
/// be null or empty string.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <returns>The exception that was thrown, when successful</returns>
public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName)
{
return ThrowsArgumentInternal<ArgumentException>(testCode, paramName, "Value cannot be null or an empty string.");
}
/// <summary>
/// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot
/// be null or empty string.
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <returns>The exception that was thrown, when successful</returns>
public static Task<ArgumentException> ThrowsArgumentNullOrEmptyStringAsync(Func<Task> testCode, string paramName)
{
return ThrowsArgumentAsyncInternal<ArgumentException>(testCode, paramName, "Value cannot be null or an empty string.");
}
/// <summary>
/// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it).
/// </summary>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <param name="paramName">The name of the parameter that should throw the exception</param>
/// <param name="exceptionMessage">The exception message to verify</param>
/// <param name="actualValue">The actual value provided</param>
/// <returns>The exception that was thrown, when successful</returns>
public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, object actualValue = null)
{
var ex = ThrowsArgumentInternal<ArgumentOutOfRangeException>(testCode, paramName, exceptionMessage);
if (paramName != null)
{
Assert.Equal(paramName, ex.ParamName);
}
if (actualValue != null)
{
Assert.Equal(actualValue, ex.ActualValue);
}
return ex;
}
// We've re-implemented all the xUnit.net Throws code so that we can get this
// updated implementation of RecordException which silently unwraps any instances
// of AggregateException. In addition to unwrapping exceptions, this method ensures
// that tests are executed in with a known set of Culture and UICulture. This prevents
// tests from failing when executed on a non-English machine.
private static Exception RecordException(Action testCode)
{
try
{
using (new CultureReplacer())
{
testCode();
}
return null;
}
catch (Exception exception)
{
return UnwrapException(exception);
}
}
private static Exception UnwrapException(Exception exception)
{
var aggEx = exception as AggregateException;
return aggEx != null ? aggEx.GetBaseException() : exception;
}
private static TException VerifyException<TException>(Exception exception)
{
var tie = exception as TargetInvocationException;
if (tie != null)
{
exception = tie.InnerException;
}
Assert.NotNull(exception);
return Assert.IsAssignableFrom<TException>(exception);
}
private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false)
{
if (expectedMessage != null)
{
if (!partialMatch)
{
Assert.Equal(expectedMessage, exception.Message);
}
else
{
Assert.Contains(expectedMessage, exception.Message);
}
}
}
}
}

View File

@ -0,0 +1,158 @@
// 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.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Lightweight version of HttpClient implemented using Socket and SslStream.
/// </summary>
public static class HttpClientSlim
{
public static async Task<string> GetStringAsync(string requestUri, bool validateCertificate = true)
=> await GetStringAsync(new Uri(requestUri), validateCertificate).ConfigureAwait(false);
public static async Task<string> GetStringAsync(Uri requestUri, bool validateCertificate = true)
{
using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false))
{
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true))
{
await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false);
await writer.WriteAsync("\r\n").ConfigureAwait(false);
}
return await ReadResponse(stream).ConfigureAwait(false);
}
}
internal static string GetHost(Uri requestUri)
{
var authority = requestUri.Authority;
if (requestUri.HostNameType == UriHostNameType.IPv6)
{
// Make sure there's no % scope id. https://github.com/aspnet/KestrelHttpServer/issues/2637
var address = IPAddress.Parse(requestUri.Host);
address = new IPAddress(address.GetAddressBytes()); // Drop scope Id.
if (requestUri.IsDefaultPort)
{
authority = $"[{address}]";
}
else
{
authority = $"[{address}]:{requestUri.Port.ToString(CultureInfo.InvariantCulture)}";
}
}
return authority;
}
public static async Task<string> PostAsync(string requestUri, HttpContent content, bool validateCertificate = true)
=> await PostAsync(new Uri(requestUri), content, validateCertificate).ConfigureAwait(false);
public static async Task<string> PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true)
{
using (var stream = await GetStream(requestUri, validateCertificate))
{
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true))
{
await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false);
await writer.WriteAsync("\r\n").ConfigureAwait(false);
}
await content.CopyToAsync(stream).ConfigureAwait(false);
return await ReadResponse(stream).ConfigureAwait(false);
}
}
private static async Task<string> ReadResponse(Stream stream)
{
using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: true,
bufferSize: 1024, leaveOpen: true))
{
var response = await reader.ReadToEndAsync().ConfigureAwait(false);
var status = GetStatus(response);
new HttpResponseMessage(status).EnsureSuccessStatusCode();
var body = response.Substring(response.IndexOf("\r\n\r\n") + 4);
return body;
}
}
private static HttpStatusCode GetStatus(string response)
{
var statusStart = response.IndexOf(' ') + 1;
var statusEnd = response.IndexOf(' ', statusStart) - 1;
var statusLength = statusEnd - statusStart + 1;
if (statusLength < 1)
{
throw new InvalidDataException($"No StatusCode found in '{response}'");
}
return (HttpStatusCode)int.Parse(response.Substring(statusStart, statusLength));
}
private static async Task<Stream> GetStream(Uri requestUri, bool validateCertificate)
{
var socket = await GetSocket(requestUri);
var stream = new NetworkStream(socket, ownsSocket: true);
if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, userCertificateValidationCallback:
validateCertificate ? null : (RemoteCertificateValidationCallback)((a, b, c, d) => true));
await sslStream.AuthenticateAsClientAsync(requestUri.Host, clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
checkCertificateRevocation: validateCertificate).ConfigureAwait(false);
return sslStream;
}
else
{
return stream;
}
}
public static async Task<Socket> GetSocket(Uri requestUri)
{
var tcs = new TaskCompletionSource<Socket>();
var socketArgs = new SocketAsyncEventArgs();
socketArgs.RemoteEndPoint = new DnsEndPoint(requestUri.DnsSafeHost, requestUri.Port);
socketArgs.Completed += (s, e) => tcs.TrySetResult(e.ConnectSocket);
// Must use static ConnectAsync(), since instance Connect() does not support DNS names on OSX/Linux.
if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, socketArgs))
{
await tcs.Task.ConfigureAwait(false);
}
var socket = socketArgs.ConnectSocket;
if (socket == null)
{
throw new SocketException((int)socketArgs.SocketError);
}
else
{
return socket;
}
}
}
}

View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Various helpers for writing tests that use ASP.NET Core.</Description>
<TargetFrameworks>netstandard2.0;net46</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore</PackageTags>
<EnableApiCheck>false</EnableApiCheck>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Win32.Registry" />
<Reference Include="System.ValueTuple" />
<Reference Include="xunit.assert" />
<Reference Include="xunit.extensibility.execution" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net46'">
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
</ItemGroup>
<ItemGroup>
<Compile Remove="contentFiles\cs\netstandard2.0\EventSourceTestCollection.cs" />
<Content Include="contentFiles\cs\netstandard2.0\EventSourceTestCollection.cs">
<Pack>True</Pack>
<PackagePath>contentFiles\cs\netstandard2.0\</PackagePath>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Testing.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,70 @@
// 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.Globalization;
using System.Reflection;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Replaces the current culture and UI culture for the test.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ReplaceCultureAttribute : BeforeAfterTestAttribute
{
private const string _defaultCultureName = "en-GB";
private const string _defaultUICultureName = "en-US";
private CultureInfo _originalCulture;
private CultureInfo _originalUICulture;
/// <summary>
/// Replaces the current culture and UI culture to en-GB and en-US respectively.
/// </summary>
public ReplaceCultureAttribute() :
this(_defaultCultureName, _defaultUICultureName)
{
}
/// <summary>
/// Replaces the current culture and UI culture based on specified values.
/// </summary>
public ReplaceCultureAttribute(string currentCulture, string currentUICulture)
{
Culture = new CultureInfo(currentCulture);
UICulture = new CultureInfo(currentUICulture);
}
/// <summary>
/// The <see cref="CultureInfo.CurrentCulture"/> for the test. Defaults to en-GB.
/// </summary>
/// <remarks>
/// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We
/// want to be able to find bugs where we're accidentally relying on the Invariant instead of the
/// user's culture.
/// </remarks>
public CultureInfo Culture { get; }
/// <summary>
/// The <see cref="CultureInfo.CurrentUICulture"/> for the test. Defaults to en-US.
/// </summary>
public CultureInfo UICulture { get; }
public override void Before(MethodInfo methodUnderTest)
{
_originalCulture = CultureInfo.CurrentCulture;
_originalUICulture = CultureInfo.CurrentUICulture;
CultureInfo.CurrentCulture = Culture;
CultureInfo.CurrentUICulture = UICulture;
}
public override void After(MethodInfo methodUnderTest)
{
CultureInfo.CurrentCulture = _originalCulture;
CultureInfo.CurrentUICulture = _originalUICulture;
}
}
}

View File

@ -0,0 +1,64 @@
// 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.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Testing
{
public static class TaskExtensions
{
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default(int))
{
// Don't create a timer if the task is already completed
if (task.IsCompleted)
{
return await task;
}
var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)))
{
cts.Cancel();
return await task;
}
else
{
throw new TimeoutException(
CreateMessage(timeout, filePath, lineNumber));
}
}
public static async Task TimeoutAfter(this Task task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default(int))
{
// Don't create a timer if the task is already completed
if (task.IsCompleted)
{
await task;
return;
}
var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)))
{
cts.Cancel();
await task;
}
else
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
}
private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber)
=> string.IsNullOrEmpty(filePath)
? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms."
: $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms.";
}
}

View File

@ -0,0 +1,31 @@
// 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;
namespace Microsoft.AspNetCore.Testing
{
public class TestPathUtilities
{
public static string GetSolutionRootDirectory(string solution)
{
var applicationBasePath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln"));
if (projectFileInfo.Exists)
{
return projectFileInfo.DirectoryName;
}
directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null);
throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories.");
}
}
}

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.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Testing
{
public static class TestPlatformHelper
{
public static bool IsMono =>
Type.GetType("Mono.Runtime") != null;
public static bool IsWindows =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsLinux =>
RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public static bool IsMac =>
RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
namespace Microsoft.AspNetCore.Testing.Tracing
{
public class CollectingEventListener : EventListener
{
private ConcurrentQueue<EventWrittenEventArgs> _events = new ConcurrentQueue<EventWrittenEventArgs>();
private object _lock = new object();
private Dictionary<string, EventSource> _existingSources = new Dictionary<string, EventSource>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _requestedEventSources = new HashSet<string>();
public void CollectFrom(string eventSourceName)
{
lock(_lock)
{
// Check if it's already been created
if(_existingSources.TryGetValue(eventSourceName, out var existingSource))
{
// It has, so just enable it now
CollectFrom(existingSource);
}
else
{
// It hasn't, so queue this request for when it is created
_requestedEventSources.Add(eventSourceName);
}
}
}
public void CollectFrom(EventSource eventSource) => EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All);
public IReadOnlyList<EventWrittenEventArgs> GetEventsWritten() => _events.ToArray();
protected override void OnEventSourceCreated(EventSource eventSource)
{
lock (_lock)
{
// Add this to the list of existing sources for future CollectEventsFrom requests.
_existingSources[eventSource.Name] = eventSource;
// Check if we have a pending request to enable it
if (_requestedEventSources.Contains(eventSource.Name))
{
CollectFrom(eventSource);
}
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
_events.Enqueue(eventData);
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Testing.Tracing
{
public class EventAssert
{
private readonly int _expectedId;
private readonly string _expectedName;
private readonly EventLevel _expectedLevel;
private readonly IList<(string name, Action<object> asserter)> _payloadAsserters = new List<(string, Action<object>)>();
public EventAssert(int expectedId, string expectedName, EventLevel expectedLevel)
{
_expectedId = expectedId;
_expectedName = expectedName;
_expectedLevel = expectedLevel;
}
public static void Collection(IEnumerable<EventWrittenEventArgs> events, params EventAssert[] asserts)
{
Assert.Collection(
events,
asserts.Select(a => a.CreateAsserter()).ToArray());
}
public static EventAssert Event(int id, string name, EventLevel level)
{
return new EventAssert(id, name, level);
}
public EventAssert Payload(string name, object expectedValue) => Payload(name, actualValue => Assert.Equal(expectedValue, actualValue));
public EventAssert Payload(string name, Action<object> asserter)
{
_payloadAsserters.Add((name, asserter));
return this;
}
private Action<EventWrittenEventArgs> CreateAsserter() => Execute;
private void Execute(EventWrittenEventArgs evt)
{
Assert.Equal(_expectedId, evt.EventId);
Assert.Equal(_expectedName, evt.EventName);
Assert.Equal(_expectedLevel, evt.Level);
Action<string> CreateNameAsserter((string name, Action<object> asserter) val)
{
return actualValue => Assert.Equal(val.name, actualValue);
}
Assert.Collection(evt.PayloadNames, _payloadAsserters.Select(CreateNameAsserter).ToArray());
Assert.Collection(evt.Payload, _payloadAsserters.Select(t => t.asserter).ToArray());
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using Xunit;
namespace Microsoft.AspNetCore.Testing.Tracing
{
// This collection attribute is what makes the "magic" happen. It forces xunit to run all tests that inherit from this
// base class sequentially, preventing conflicts (since EventSource/EventListener is a process-global concept).
[Collection(CollectionName)]
public abstract class EventSourceTestBase : IDisposable
{
public const string CollectionName = "Microsoft.AspNetCore.Testing.Tracing.EventSourceTestCollection";
private readonly CollectingEventListener _listener;
public EventSourceTestBase()
{
_listener = new CollectingEventListener();
}
protected void CollectFrom(string eventSourceName)
{
_listener.CollectFrom(eventSourceName);
}
protected void CollectFrom(EventSource eventSource)
{
_listener.CollectFrom(eventSource);
}
protected IReadOnlyList<EventWrittenEventArgs> GetEvents() => _listener.GetEventsWritten();
public void Dispose()
{
_listener.Dispose();
}
}
}

View File

@ -0,0 +1,10 @@
namespace Microsoft.AspNetCore.Testing.Tracing
{
// This file comes from Microsoft.AspNetCore.Testing and has to be defined in the test assembly.
// It enables EventSourceTestBase's parallel isolation functionality.
[Xunit.CollectionDefinition(EventSourceTestBase.CollectionName, DisableParallelization = true)]
public class EventSourceTestCollection
{
}
}

View File

@ -0,0 +1,15 @@
// 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 Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalFactDiscoverer), "Microsoft.AspNetCore.Testing")]
public class ConditionalFactAttribute : FactAttribute
{
}
}

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 Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
internal class ConditionalFactDiscoverer : FactDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
public ConditionalFactDiscoverer(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(), testMethod)
: base.CreateTestCase(discoveryOptions, testMethod, factAttribute);
}
}
}

View File

@ -0,0 +1,15 @@
// 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 Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalTheoryDiscoverer), "Microsoft.AspNetCore.Testing")]
public class ConditionalTheoryAttribute : TheoryAttribute
{
}
}

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 System.Collections.Generic;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
internal class ConditionalTheoryDiscoverer : TheoryDiscoverer
{
public ConditionalTheoryDiscoverer(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(), 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

@ -0,0 +1,38 @@
// 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.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Testing.xunit
{
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class DockerOnlyAttribute : Attribute, ITestCondition
{
public string SkipReason { get; } = "This test can only run in a Docker container.";
public bool IsMet
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// we currently don't have a good way to detect if running in a Windows container
return false;
}
const string procFile = "/proc/1/cgroup";
if (!File.Exists(procFile))
{
return false;
}
var lines = File.ReadAllLines(procFile);
// typically the last line in the file is "1:name=openrc:/docker"
return lines.Reverse().Any(l => l.EndsWith("name=openrc:/docker", StringComparison.Ordinal));
}
}
}
}

View File

@ -0,0 +1,95 @@
// 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.Linq;
namespace Microsoft.AspNetCore.Testing.xunit
{
/// <summary>
/// Skips a test when the value of an environment variable matches any of the supplied values.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestCondition
{
private readonly string _variableName;
private readonly string[] _values;
private string _currentValue;
private readonly IEnvironmentVariable _environmentVariable;
/// <summary>
/// Creates a new instance of <see cref="EnvironmentVariableSkipConditionAttribute"/>.
/// </summary>
/// <param name="variableName">Name of the environment variable.</param>
/// <param name="values">Value(s) of the environment variable to match for the test to be skipped</param>
public EnvironmentVariableSkipConditionAttribute(string variableName, params string[] values)
: this(new EnvironmentVariable(), variableName, values)
{
}
// To enable unit testing
internal EnvironmentVariableSkipConditionAttribute(
IEnvironmentVariable environmentVariable,
string variableName,
params string[] values)
{
if (environmentVariable == null)
{
throw new ArgumentNullException(nameof(environmentVariable));
}
if (variableName == null)
{
throw new ArgumentNullException(nameof(variableName));
}
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
_variableName = variableName;
_values = values;
_environmentVariable = environmentVariable;
}
/// <summary>
/// Skips the test only if the value of the variable matches any of the supplied values. Default is <c>True</c>.
/// </summary>
public bool SkipOnMatch { get; set; } = true;
public bool IsMet
{
get
{
_currentValue = _environmentVariable.Get(_variableName);
var hasMatched = _values.Any(value => string.Compare(value, _currentValue, ignoreCase: true) == 0);
if (SkipOnMatch)
{
return hasMatched;
}
else
{
return !hasMatched;
}
}
}
public string SkipReason
{
get
{
var value = _currentValue == null ? "(null)" : _currentValue;
return $"Test skipped on environment variable with name '{_variableName}' and value '{value}' " +
$"for the '{nameof(SkipOnMatch)}' value of '{SkipOnMatch}'.";
}
}
private struct EnvironmentVariable : IEnvironmentVariable
{
public string Get(string name)
{
return Environment.GetEnvironmentVariable(name);
}
}
}
}

View File

@ -0,0 +1,57 @@
// 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.xunit
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class FrameworkSkipConditionAttribute : Attribute, ITestCondition
{
private readonly RuntimeFrameworks _excludedFrameworks;
public FrameworkSkipConditionAttribute(RuntimeFrameworks excludedFrameworks)
{
_excludedFrameworks = excludedFrameworks;
}
public bool IsMet
{
get
{
return CanRunOnThisFramework(_excludedFrameworks);
}
}
public string SkipReason { get; set; } = "Test cannot run on this runtime framework.";
private static bool CanRunOnThisFramework(RuntimeFrameworks excludedFrameworks)
{
if (excludedFrameworks == RuntimeFrameworks.None)
{
return true;
}
#if NET461 || NET46
if (excludedFrameworks.HasFlag(RuntimeFrameworks.Mono) &&
TestPlatformHelper.IsMono)
{
return false;
}
if (excludedFrameworks.HasFlag(RuntimeFrameworks.CLR))
{
return false;
}
#elif NETSTANDARD2_0
if (excludedFrameworks.HasFlag(RuntimeFrameworks.CoreCLR))
{
return false;
}
#else
#error Target frameworks need to be updated.
#endif
return true;
}
}
}

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.xunit
{
internal interface IEnvironmentVariable
{
string Get(string name);
}
}

View File

@ -0,0 +1,12 @@
// 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.xunit
{
public interface ITestCondition
{
bool IsMet { get; }
string SkipReason { get; }
}
}

View File

@ -0,0 +1,111 @@
// 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.Runtime.InteropServices;
using Microsoft.Win32;
namespace Microsoft.AspNetCore.Testing.xunit
{
/// <summary>
/// Skips a test if the OS is the given type (Windows) and the OS version is less than specified.
/// E.g. Specifying Window 10.0 skips on Win 8, but not on Linux. Combine with OSSkipConditionAttribute as needed.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class MinimumOSVersionAttribute : Attribute, ITestCondition
{
private readonly OperatingSystems _excludedOperatingSystem;
private readonly Version _minVersion;
private readonly OperatingSystems _osPlatform;
private readonly Version _osVersion;
public MinimumOSVersionAttribute(OperatingSystems operatingSystem, string minVersion) :
this(
operatingSystem,
GetCurrentOS(),
GetCurrentOSVersion(),
Version.Parse(minVersion))
{
}
// to enable unit testing
internal MinimumOSVersionAttribute(
OperatingSystems operatingSystem, OperatingSystems osPlatform, Version osVersion, Version minVersion)
{
if (operatingSystem != OperatingSystems.Windows)
{
throw new NotImplementedException("Min version support is only implemented for Windows.");
}
_excludedOperatingSystem = operatingSystem;
_minVersion = minVersion;
_osPlatform = osPlatform;
_osVersion = osVersion;
SkipReason = $"This test requires {_excludedOperatingSystem} {_minVersion} or later.";
}
public bool IsMet
{
get
{
// Do not skip other OS's, Use OSSkipConditionAttribute or a separate MinimumOSVersionAttribute for that.
if (_osPlatform != _excludedOperatingSystem)
{
return true;
}
return _osVersion >= _minVersion;
}
}
public string SkipReason { get; set; }
private static OperatingSystems GetCurrentOS()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return OperatingSystems.Windows;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return OperatingSystems.Linux;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return OperatingSystems.MacOSX;
}
throw new PlatformNotSupportedException();
}
private static Version GetCurrentOSVersion()
{
// currently not used on other OS's
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Win10+
var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
var major = key.GetValue("CurrentMajorVersionNumber") as int?;
var minor = key.GetValue("CurrentMinorVersionNumber") as int?;
if (major.HasValue && minor.HasValue)
{
return new Version(major.Value, minor.Value);
}
// CurrentVersion doesn't work past Win8.1
var current = key.GetValue("CurrentVersion") as string;
if (!string.IsNullOrEmpty(current) && Version.TryParse(current, out var currentVersion))
{
return currentVersion;
}
// Environment.OSVersion doesn't work past Win8.
return Environment.OSVersion.Version;
}
else
{
return new Version();
}
}
}
}

View File

@ -0,0 +1,99 @@
// 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.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Testing.xunit
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class OSSkipConditionAttribute : Attribute, ITestCondition
{
private readonly OperatingSystems _excludedOperatingSystem;
private readonly IEnumerable<string> _excludedVersions;
private readonly OperatingSystems _osPlatform;
private readonly string _osVersion;
public OSSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) :
this(
operatingSystem,
GetCurrentOS(),
GetCurrentOSVersion(),
versions)
{
}
// to enable unit testing
internal OSSkipConditionAttribute(
OperatingSystems operatingSystem, OperatingSystems osPlatform, string osVersion, params string[] versions)
{
_excludedOperatingSystem = operatingSystem;
_excludedVersions = versions ?? Enumerable.Empty<string>();
_osPlatform = osPlatform;
_osVersion = osVersion;
}
public bool IsMet
{
get
{
var currentOSInfo = new OSInfo()
{
OperatingSystem = _osPlatform,
Version = _osVersion,
};
var skip = (_excludedOperatingSystem & currentOSInfo.OperatingSystem) == currentOSInfo.OperatingSystem;
if (_excludedVersions.Any())
{
skip = skip
&& _excludedVersions.Any(ex => _osVersion.StartsWith(ex, StringComparison.OrdinalIgnoreCase));
}
// Since a test would be excuted only if 'IsMet' is true, return false if we want to skip
return !skip;
}
}
public string SkipReason { get; set; } = "Test cannot run on this operating system.";
static private OperatingSystems GetCurrentOS()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return OperatingSystems.Windows;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return OperatingSystems.Linux;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return OperatingSystems.MacOSX;
}
throw new PlatformNotSupportedException();
}
static private string GetCurrentOSVersion()
{
// currently not used on other OS's
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Environment.OSVersion.Version.ToString();
}
else
{
return string.Empty;
}
}
private class OSInfo
{
public OperatingSystems OperatingSystem { get; set; }
public string Version { get; set; }
}
}
}

View File

@ -0,0 +1,15 @@
// 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.xunit
{
[Flags]
public enum OperatingSystems
{
Linux = 1,
MacOSX = 2,
Windows = 4,
}
}

View File

@ -0,0 +1,16 @@
// 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.xunit
{
[Flags]
public enum RuntimeFrameworks
{
None = 0,
Mono = 1 << 0,
CLR = 1 << 1,
CoreCLR = 1 << 2
}
}

View File

@ -0,0 +1,40 @@
// 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 Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
public class SkippedTestCase : XunitTestCase
{
private string _skipReason;
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public SkippedTestCase() : base()
{
}
public SkippedTestCase(string skipReason, IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null)
: base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments)
{
_skipReason = skipReason;
}
protected override string GetSkipReason(IAttributeInfo factAttribute)
=> _skipReason ?? base.GetSkipReason(factAttribute);
public override void Deserialize(IXunitSerializationInfo data)
{
base.Deserialize(data);
_skipReason = data.GetValue<string>(nameof(_skipReason));
}
public override void Serialize(IXunitSerializationInfo data)
{
base.Serialize(data);
data.AddValue(nameof(_skipReason), _skipReason);
}
}
}

View File

@ -0,0 +1,34 @@
// 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.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing.xunit
{
public static class TestMethodExtensions
{
public static string EvaluateSkipConditions(this ITestMethod testMethod)
{
var testClass = testMethod.TestClass.Class;
var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly;
var conditionAttributes = testMethod.Method
.GetCustomAttributes(typeof(ITestCondition))
.Concat(testClass.GetCustomAttributes(typeof(ITestCondition)))
.Concat(assembly.GetCustomAttributes(typeof(ITestCondition)))
.OfType<ReflectionAttributeInfo>()
.Select(attributeInfo => attributeInfo.Attribute);
foreach (ITestCondition condition in conditionAttributes)
{
if (!condition.IsMet)
{
return condition.SkipReason;
}
}
return null;
}
}
}

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.
namespace Microsoft.AspNetCore.Testing.xunit
{
public static class WindowsVersions
{
public const string Win7 = "6.1";
public const string Win2008R2 = Win7;
public const string Win8 = "6.2";
public const string Win81 = "6.3";
public const string Win10 = "10.0";
}
}

View File

@ -0,0 +1,87 @@
using System.Diagnostics.Tracing;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.Tracing;
using Xunit;
namespace Microsoft.AspNetCore.Testing.Tests
{
// We are verifying here that when event listener tests are spread among multiple classes, they still
// work, even when run in parallel. To do that we have a bunch of tests in different classes (since
// that affects parallelism) and do some Task.Yielding in them.
public class CollectingEventListenerTests
{
public abstract class CollectingTestBase : EventSourceTestBase
{
[Fact]
public async Task CollectingEventListenerTest()
{
CollectFrom("Microsoft-AspNetCore-Testing-Test");
await Task.Yield();
TestEventSource.Log.Test();
await Task.Yield();
TestEventSource.Log.TestWithPayload(42, 4.2);
await Task.Yield();
var events = GetEvents();
EventAssert.Collection(events,
EventAssert.Event(1, "Test", EventLevel.Informational),
EventAssert.Event(2, "TestWithPayload", EventLevel.Verbose)
.Payload("payload1", 42)
.Payload("payload2", 4.2));
}
}
// These tests are designed to interfere with the collecting ones by running in parallel and writing events
public abstract class NonCollectingTestBase
{
[Fact]
public async Task CollectingEventListenerTest()
{
await Task.Yield();
TestEventSource.Log.Test();
await Task.Yield();
TestEventSource.Log.TestWithPayload(42, 4.2);
await Task.Yield();
}
}
public class CollectingTests
{
public class A : CollectingTestBase { }
public class B : CollectingTestBase { }
public class C : CollectingTestBase { }
public class D : CollectingTestBase { }
public class E : CollectingTestBase { }
public class F : CollectingTestBase { }
public class G : CollectingTestBase { }
}
public class NonCollectingTests
{
public class A : NonCollectingTestBase { }
public class B : NonCollectingTestBase { }
public class C : NonCollectingTestBase { }
public class D : NonCollectingTestBase { }
public class E : NonCollectingTestBase { }
public class F : NonCollectingTestBase { }
public class G : NonCollectingTestBase { }
}
}
[EventSource(Name = "Microsoft-AspNetCore-Testing-Test")]
public class TestEventSource : EventSource
{
public static readonly TestEventSource Log = new TestEventSource();
private TestEventSource()
{
}
[Event(eventId: 1, Level = EventLevel.Informational, Message = "Test")]
public void Test() => WriteEvent(1);
[Event(eventId: 2, Level = EventLevel.Verbose, Message = "Test")]
public void TestWithPayload(int payload1, double payload2) => WriteEvent(2, payload1, payload2);
}
}

View File

@ -0,0 +1,60 @@
// 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.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class ConditionalFactTest : IClassFixture<ConditionalFactTest.ConditionalFactAsserter>
{
public ConditionalFactTest(ConditionalFactAsserter collector)
{
Asserter = collector;
}
private ConditionalFactAsserter Asserter { get; }
[Fact]
public void TestAlwaysRun()
{
// This is required to ensure that the type at least gets initialized.
Assert.True(true);
}
[ConditionalFact(Skip = "Test is always skipped.")]
public void ConditionalFactSkip()
{
Assert.True(false, "This test should always be skipped.");
}
#if NETCOREAPP2_2
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public void ThisTestMustRunOnCoreCLR()
{
Asserter.TestRan = true;
}
#elif NET461 || NET46
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)]
public void ThisTestMustRunOnCLR()
{
Asserter.TestRan = true;
}
#else
#error Target frameworks need to be updated.
#endif
public class ConditionalFactAsserter : IDisposable
{
public bool TestRan { get; set; }
public void Dispose()
{
Assert.True(TestRan, "If this assertion fails, a conditional fact wasn't discovered.");
}
}
}
}

View File

@ -0,0 +1,156 @@
// 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.xunit;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Testing
{
public class ConditionalTheoryTest : IClassFixture<ConditionalTheoryTest.ConditionalTheoryAsserter>
{
public ConditionalTheoryTest(ConditionalTheoryAsserter asserter)
{
Asserter = asserter;
}
public ConditionalTheoryAsserter Asserter { get; }
[ConditionalTheory(Skip = "Test is always skipped.")]
[InlineData(0)]
public void ConditionalTheorySkip(int arg)
{
Assert.True(false, "This test should always be skipped.");
}
private static int _conditionalTheoryRuns = 0;
[ConditionalTheory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2, Skip = "Skip these data")]
public void ConditionalTheoryRunOncePerDataLine(int arg)
{
_conditionalTheoryRuns++;
Assert.True(_conditionalTheoryRuns <= 2, $"Theory should run 2 times, but ran {_conditionalTheoryRuns} times.");
}
[ConditionalTheory, Trait("Color", "Blue")]
[InlineData(1)]
public void ConditionalTheoriesShouldPreserveTraits(int arg)
{
Assert.True(true);
}
[ConditionalTheory(Skip = "Skip this")]
[MemberData(nameof(GetInts))]
public void ConditionalTheoriesWithSkippedMemberData(int arg)
{
Assert.True(false, "This should never run");
}
private static int _conditionalMemberDataRuns = 0;
[ConditionalTheory]
[InlineData(4)]
[MemberData(nameof(GetInts))]
public void ConditionalTheoriesWithMemberData(int arg)
{
_conditionalMemberDataRuns++;
Assert.True(_conditionalTheoryRuns <= 3, $"Theory should run 2 times, but ran {_conditionalMemberDataRuns} times.");
}
public static TheoryData<int> GetInts
=> new TheoryData<int> { 0, 1 };
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Linux)]
[MemberData(nameof(GetActionTestData))]
public void ConditionalTheoryWithFuncs(Func<int, int> func)
{
Assert.True(false, "This should never run");
}
[Fact]
public void TestAlwaysRun()
{
// This is required to ensure that this type at least gets initialized.
Assert.True(true);
}
#if NETCOREAPP2_2
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
[MemberData(nameof(GetInts))]
public void ThisTestMustRunOnCoreCLR(int value)
{
Asserter.TestRan = true;
}
#elif NET461 || NET46
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)]
[MemberData(nameof(GetInts))]
public void ThisTestMustRunOnCLR(int value)
{
Asserter.TestRan = true;
}
#else
#error Target frameworks need to be updated.
#endif
public static TheoryData<Func<int, int>> GetActionTestData
=> new TheoryData<Func<int, int>>
{
(i) => i * 1
};
public class ConditionalTheoryAsserter : IDisposable
{
public bool TestRan { get; set; }
public void Dispose()
{
Assert.True(TestRan, "If this assertion fails, a conditional theory wasn't discovered.");
}
}
[ConditionalTheory]
[MemberData(nameof(SkippableData))]
public void WithSkipableData(Skippable skippable)
{
Assert.Null(skippable.Skip);
Assert.Equal(1, skippable.Data);
}
public static TheoryData<Skippable> SkippableData => new TheoryData<Skippable>
{
new Skippable() { Data = 1 },
new Skippable() { Data = 2, Skip = "This row should be skipped." }
};
public class Skippable : IXunitSerializable
{
public Skippable() { }
public int Data { get; set; }
public string Skip { get; set; }
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(Data), Data, typeof(int));
}
public void Deserialize(IXunitSerializationInfo info)
{
Data = info.GetValue<int>(nameof(Data));
}
public override string ToString()
{
return Data.ToString();
}
}
}
}

View File

@ -0,0 +1,21 @@
// 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.Runtime.InteropServices;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class DockerTests
{
[ConditionalFact]
[DockerOnly]
[Trait("Docker", "true")]
public void DoesNotRunOnWindows()
{
Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
}
}
}

View File

@ -0,0 +1,166 @@
// 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.xunit
{
public class EnvironmentVariableSkipConditionTest
{
private readonly string _skipReason = "Test skipped on environment variable with name '{0}' and value '{1}'" +
$" for the '{nameof(EnvironmentVariableSkipConditionAttribute.SkipOnMatch)}' value of '{{2}}'.";
[Theory]
[InlineData("false")]
[InlineData("")]
[InlineData(null)]
public void IsMet_DoesNotMatch(string environmentVariableValue)
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable(environmentVariableValue),
"Run",
"true");
// Act
var isMet = attribute.IsMet;
// Assert
Assert.False(isMet);
}
[Theory]
[InlineData("True")]
[InlineData("TRUE")]
[InlineData("true")]
public void IsMet_DoesCaseInsensitiveMatch_OnValue(string environmentVariableValue)
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable(environmentVariableValue),
"Run",
"true");
// Act
var isMet = attribute.IsMet;
// Assert
Assert.True(isMet);
Assert.Equal(
string.Format(_skipReason, "Run", environmentVariableValue, attribute.SkipOnMatch),
attribute.SkipReason);
}
[Fact]
public void IsMet_DoesSuccessfulMatch_OnNull()
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable(null),
"Run",
"true", null); // skip the test when the variable 'Run' is explicitly set to 'true' or is null (default)
// Act
var isMet = attribute.IsMet;
// Assert
Assert.True(isMet);
Assert.Equal(
string.Format(_skipReason, "Run", "(null)", attribute.SkipOnMatch),
attribute.SkipReason);
}
[Theory]
[InlineData("false")]
[InlineData("")]
[InlineData(null)]
public void IsMet_MatchesOnMultipleSkipValues(string environmentVariableValue)
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable(environmentVariableValue),
"Run",
"false", "", null);
// Act
var isMet = attribute.IsMet;
// Assert
Assert.True(isMet);
}
[Fact]
public void IsMet_DoesNotMatch_OnMultipleSkipValues()
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable("100"),
"Build",
"125", "126");
// Act
var isMet = attribute.IsMet;
// Assert
Assert.False(isMet);
}
[Theory]
[InlineData("CentOS")]
[InlineData(null)]
[InlineData("")]
public void IsMet_Matches_WhenSkipOnMatchIsFalse(string environmentVariableValue)
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable(environmentVariableValue),
"LinuxFlavor",
"Ubuntu14.04")
{
// Example: Run this test on all OSes except on "Ubuntu14.04"
SkipOnMatch = false
};
// Act
var isMet = attribute.IsMet;
// Assert
Assert.True(isMet);
}
[Fact]
public void IsMet_DoesNotMatch_WhenSkipOnMatchIsFalse()
{
// Arrange
var attribute = new EnvironmentVariableSkipConditionAttribute(
new TestEnvironmentVariable("Ubuntu14.04"),
"LinuxFlavor",
"Ubuntu14.04")
{
// Example: Run this test on all OSes except on "Ubuntu14.04"
SkipOnMatch = false
};
// Act
var isMet = attribute.IsMet;
// Assert
Assert.False(isMet);
}
private struct TestEnvironmentVariable : IEnvironmentVariable
{
public TestEnvironmentVariable(string value)
{
Value = value;
}
public string Value { get; private set; }
public string Get(string name)
{
return Value;
}
}
}
}

View File

@ -0,0 +1,39 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class ExceptionAssertTest
{
[Fact]
[ReplaceCulture("fr-FR", "fr-FR")]
public void AssertArgumentNullOrEmptyString_WorksInNonEnglishCultures()
{
// Arrange
Action action = () =>
{
throw new ArgumentException("Value cannot be null or an empty string.", "foo");
};
// Act and Assert
ExceptionAssert.ThrowsArgumentNullOrEmptyString(action, "foo");
}
[Fact]
[ReplaceCulture("fr-FR", "fr-FR")]
public void AssertArgumentOutOfRangeException_WorksInNonEnglishCultures()
{
// Arrange
Action action = () =>
{
throw new ArgumentOutOfRangeException("foo", 10, "exception message.");
};
// Act and Assert
ExceptionAssert.ThrowsArgumentOutOfRange(action, "foo", "exception message.", 10);
}
}
}

View File

@ -0,0 +1,117 @@
// 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.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class HttpClientSlimTest
{
private static byte[] _defaultResponse = Encoding.ASCII.GetBytes("test");
[Fact]
public async Task GetStringAsyncHttp()
{
using (var host = StartHost(out var address))
{
Assert.Equal("test", await HttpClientSlim.GetStringAsync(address));
}
}
[Fact]
public async Task GetStringAsyncThrowsForErrorResponse()
{
using (var host = StartHost(out var address, statusCode: 500))
{
await Assert.ThrowsAnyAsync<HttpRequestException>(() => HttpClientSlim.GetStringAsync(address));
}
}
[Fact]
public async Task PostAsyncHttp()
{
using (var host = StartHost(out var address, handler: context => context.Request.InputStream.CopyToAsync(context.Response.OutputStream)))
{
Assert.Equal("test post", await HttpClientSlim.PostAsync(address, new StringContent("test post")));
}
}
[Fact]
public async Task PostAsyncThrowsForErrorResponse()
{
using (var host = StartHost(out var address, statusCode: 500))
{
await Assert.ThrowsAnyAsync<HttpRequestException>(
() => HttpClientSlim.PostAsync(address, new StringContent("")));
}
}
[Fact]
public void Ipv6ScopeIdsFilteredOut()
{
var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:5003/");
Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]:5003", HttpClientSlim.GetHost(requestUri));
}
[Fact]
public void GetHostExcludesDefaultPort()
{
var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:80/");
Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]", HttpClientSlim.GetHost(requestUri));
}
private HttpListener StartHost(out string address, int statusCode = 200, Func<HttpListenerContext, Task> handler = null)
{
var listener = new HttpListener();
var random = new Random();
address = null;
for (var i = 0; i < 10; i++)
{
try
{
// HttpListener doesn't support requesting port 0 (dynamic).
// Requesting port 0 from Sockets and then passing that to HttpListener is racy.
// Just keep trying until we find a free one.
address = $"http://127.0.0.1:{random.Next(1024, ushort.MaxValue)}/";
listener.Prefixes.Add(address);
listener.Start();
break;
}
catch (HttpListenerException)
{
// Address in use
listener.Close();
listener = new HttpListener();
}
}
Assert.True(listener.IsListening, "IsListening");
_ = listener.GetContextAsync().ContinueWith(async task =>
{
var context = task.Result;
context.Response.StatusCode = statusCode;
if (handler == null)
{
await context.Response.OutputStream.WriteAsync(_defaultResponse, 0, _defaultResponse.Length);
}
else
{
await handler(context);
}
context.Response.Close();
});
return listener;
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<!-- allow skipped tests -->
<NoWarn>$(NoWarn);xUnit1004</NoWarn>
<!-- allow unused theory parameters -->
<NoWarn>$(NoWarn);xUnit1026</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\src\contentFiles\cs\netstandard2.0\EventSourceTestCollection.cs" Link="EventSourceTestCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\Microsoft.AspNetCore.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,132 @@
// 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.Runtime.InteropServices;
using Xunit;
namespace Microsoft.AspNetCore.Testing.xunit
{
public class OSSkipConditionAttributeTest
{
[Fact]
public void Skips_WhenOnlyOperatingSystemIsSupplied()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Windows,
OperatingSystems.Windows,
"2.5");
// Assert
Assert.False(osSkipAttribute.IsMet);
}
[Fact]
public void DoesNotSkip_WhenOperatingSystemDoesNotMatch()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Linux,
OperatingSystems.Windows,
"2.5");
// Assert
Assert.True(osSkipAttribute.IsMet);
}
[Fact]
public void DoesNotSkip_WhenVersionsDoNotMatch()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Windows,
OperatingSystems.Windows,
"2.5",
"10.0");
// Assert
Assert.True(osSkipAttribute.IsMet);
}
[Fact]
public void DoesNotSkip_WhenOnlyVersionsMatch()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Linux,
OperatingSystems.Windows,
"2.5",
"2.5");
// Assert
Assert.True(osSkipAttribute.IsMet);
}
[Theory]
[InlineData("2.5", "2.5")]
[InlineData("blue", "Blue")]
public void Skips_WhenVersionsMatches(string currentOSVersion, string skipVersion)
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Windows,
OperatingSystems.Windows,
currentOSVersion,
skipVersion);
// Assert
Assert.False(osSkipAttribute.IsMet);
}
[Fact]
public void Skips_WhenVersionsMatchesOutOfMultiple()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(
OperatingSystems.Windows,
OperatingSystems.Windows,
"2.5",
"10.0", "3.4", "2.5");
// Assert
Assert.False(osSkipAttribute.IsMet);
}
[Fact]
public void Skips_BothMacOSXAndLinux()
{
// Act
var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.Linux, string.Empty);
var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.MacOSX, string.Empty);
// Assert
Assert.False(osSkipAttributeLinux.IsMet);
Assert.False(osSkipAttributeMacOSX.IsMet);
}
[Fact]
public void Skips_BothMacOSXAndWindows()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.Windows, string.Empty);
var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.MacOSX, string.Empty);
// Assert
Assert.False(osSkipAttribute.IsMet);
Assert.False(osSkipAttributeMacOSX.IsMet);
}
[Fact]
public void Skips_BothWindowsAndLinux()
{
// Act
var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Windows, string.Empty);
var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Linux, string.Empty);
// Assert
Assert.False(osSkipAttribute.IsMet);
Assert.False(osSkipAttributeLinux.IsMet);
}
}
}

View File

@ -0,0 +1,116 @@
// 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.Runtime.InteropServices;
using Xunit;
namespace Microsoft.AspNetCore.Testing.xunit
{
public class OSSkipConditionTest
{
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux)]
public void TestSkipLinux()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Linux),
"Test should not be running on Linux");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX)]
public void TestSkipMacOSX()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.OSX),
"Test should not be running on MacOSX.");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)]
public void RunTest_DoesNotRunOnWin7OrWin2008R2()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
Environment.OSVersion.Version.ToString().StartsWith("6.1"),
"Test should not be running on Win7 or Win2008R2.");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows)]
public void TestSkipWindows()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
"Test should not be running on Windows.");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
public void TestSkipLinuxAndMacOSX()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Linux),
"Test should not be running on Linux.");
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.OSX),
"Test should not be running on MacOSX.");
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Linux)]
[InlineData(1)]
public void TestTheorySkipLinux(int arg)
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Linux),
"Test should not be running on Linux");
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData(1)]
public void TestTheorySkipMacOS(int arg)
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.OSX),
"Test should not be running on MacOSX.");
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Windows)]
[InlineData(1)]
public void TestTheorySkipWindows(int arg)
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
"Test should not be running on Windows.");
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[InlineData(1)]
public void TestTheorySkipLinuxAndMacOSX(int arg)
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Linux),
"Test should not be running on Linux.");
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.OSX),
"Test should not be running on MacOSX.");
}
}
[OSSkipCondition(OperatingSystems.Windows)]
public class OSSkipConditionClassTest
{
[ConditionalFact]
public void TestSkipClassWindows()
{
Assert.False(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
"Test should not be running on Windows.");
}
}
}

View File

@ -0,0 +1,66 @@
// 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.Globalization;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class RepalceCultureAttributeTest
{
[Fact]
public void DefaultsTo_EnGB_EnUS()
{
// Arrange
var culture = new CultureInfo("en-GB");
var uiCulture = new CultureInfo("en-US");
// Act
var replaceCulture = new ReplaceCultureAttribute();
// Assert
Assert.Equal(culture, replaceCulture.Culture);
Assert.Equal(uiCulture, replaceCulture.UICulture);
}
[Fact]
public void UsesSuppliedCultureAndUICulture()
{
// Arrange
var culture = "de-DE";
var uiCulture = "fr-CA";
// Act
var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture);
// Assert
Assert.Equal(new CultureInfo(culture), replaceCulture.Culture);
Assert.Equal(new CultureInfo(uiCulture), replaceCulture.UICulture);
}
[Fact]
public void BeforeAndAfterTest_ReplacesCulture()
{
// Arrange
var originalCulture = CultureInfo.CurrentCulture;
var originalUICulture = CultureInfo.CurrentUICulture;
var culture = "de-DE";
var uiCulture = "fr-CA";
var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture);
// Act
replaceCulture.Before(methodUnderTest: null);
// Assert
Assert.Equal(new CultureInfo(culture), CultureInfo.CurrentCulture);
Assert.Equal(new CultureInfo(uiCulture), CultureInfo.CurrentUICulture);
// Act
replaceCulture.After(methodUnderTest: null);
// Assert
Assert.Equal(originalCulture, CultureInfo.CurrentCulture);
Assert.Equal(originalUICulture, CultureInfo.CurrentUICulture);
}
}
}

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;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class TaskExtensionsTest
{
[Fact]
public async Task TimeoutAfterTest()
{
await Assert.ThrowsAsync<TimeoutException>(async () => await Task.Delay(1000).TimeoutAfter(TimeSpan.FromMilliseconds(50)));
}
}
}

View File

@ -0,0 +1,31 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class TestPathUtilitiesTest
{
[Fact]
public void GetSolutionRootDirectory_ResolvesSolutionRoot()
{
// Directory.GetCurrentDirectory() gives:
// Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\netcoreapp2.0
// Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net461
// Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net46
var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "..", ".."));
Assert.Equal(expectedPath, TestPathUtilities.GetSolutionRootDirectory("Extensions"));
}
[Fact]
public void GetSolutionRootDirectory_Throws_IfNotFound()
{
var exception = Assert.Throws<Exception>(() => TestPathUtilities.GetSolutionRootDirectory("NotTesting"));
Assert.Equal($"Solution file NotTesting.sln could not be found in {AppContext.BaseDirectory} or its parent directories.", exception.Message);
}
}
}

View File

@ -0,0 +1,55 @@
// 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.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class TestPlatformHelperTest
{
[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Windows)]
public void IsLinux_TrueOnLinux()
{
Assert.True(TestPlatformHelper.IsLinux);
Assert.False(TestPlatformHelper.IsMac);
Assert.False(TestPlatformHelper.IsWindows);
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.Windows)]
public void IsMac_TrueOnMac()
{
Assert.False(TestPlatformHelper.IsLinux);
Assert.True(TestPlatformHelper.IsMac);
Assert.False(TestPlatformHelper.IsWindows);
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public void IsWindows_TrueOnWindows()
{
Assert.False(TestPlatformHelper.IsLinux);
Assert.False(TestPlatformHelper.IsMac);
Assert.True(TestPlatformHelper.IsWindows);
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR | RuntimeFrameworks.CoreCLR | RuntimeFrameworks.None)]
public void IsMono_TrueOnMono()
{
Assert.True(TestPlatformHelper.IsMono);
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public void IsMono_FalseElsewhere()
{
Assert.False(TestPlatformHelper.IsMono);
}
}
}