Merge branch 'release/2.1' into release/2.2
\n\nCommit migrated from 18fcffbd25
This commit is contained in:
parent
5460fd093e
commit
45b0b83997
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
internal enum CertificatePurpose
|
||||
{
|
||||
All,
|
||||
HTTPS,
|
||||
Signing
|
||||
HTTPS
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<DebugType>portable</DebugType>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue