Reorganize source code

In preparation for merging many other projects into this repo, this establishes a new source code organization which groups projects together based on subject matter.
\n\nCommit migrated from 7ce647cfa3
This commit is contained in:
Nate McMaster 2018-10-30 14:18:11 -07:00
commit af034a9c5e
82 changed files with 11116 additions and 0 deletions

View File

@ -0,0 +1,108 @@
// 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.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Microsoft.Extensions.ObjectPool
{
public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{
private readonly ObjectWrapper[] _items;
private readonly IPooledObjectPolicy<T> _policy;
private readonly bool _isDefaultPolicy;
private T _firstItem;
// This class was introduced in 2.1 to avoid the interface call where possible
private readonly PooledObjectPolicy<T> _fastPolicy;
public DefaultObjectPool(IPooledObjectPolicy<T> policy)
: this(policy, Environment.ProcessorCount * 2)
{
}
public DefaultObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained)
{
_policy = policy ?? throw new ArgumentNullException(nameof(policy));
_fastPolicy = policy as PooledObjectPolicy<T>;
_isDefaultPolicy = IsDefaultPolicy();
// -1 due to _firstItem
_items = new ObjectWrapper[maximumRetained - 1];
bool IsDefaultPolicy()
{
var type = policy.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(DefaultPooledObjectPolicy<>);
}
}
public override T Get()
{
T item = _firstItem;
if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item)
{
item = GetViaScan();
}
return item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T GetViaScan()
{
ObjectWrapper[] items = _items;
for (var i = 0; i < items.Length; i++)
{
T item = items[i];
if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item)
{
return item;
}
}
return Create();
}
// Non-inline to improve its code quality as uncommon path
[MethodImpl(MethodImplOptions.NoInlining)]
private T Create() => _fastPolicy?.Create() ?? _policy.Create();
public override void Return(T obj)
{
if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj)))
{
if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null)
{
ReturnViaScan(obj);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReturnViaScan(T obj)
{
ObjectWrapper[] items = _items;
for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i)
{
}
}
[DebuggerDisplay("{Element}")]
private struct ObjectWrapper
{
public T Element;
public ObjectWrapper(T item) => Element = item;
public static implicit operator T(ObjectWrapper wrapper) => wrapper.Element;
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.ObjectPool
{
public class DefaultObjectPoolProvider : ObjectPoolProvider
{
public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2;
public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy)
{
return new DefaultObjectPool<T>(policy, MaximumRetained);
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.ObjectPool
{
public class DefaultPooledObjectPolicy<T> : PooledObjectPolicy<T> where T : class, new()
{
public override T Create()
{
return new T();
}
// DefaultObjectPool<T> doesn't call 'Return' for the default policy.
// So take care adding any logic to this method, as it might require changes elsewhere.
public override bool Return(T obj)
{
return true;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.ObjectPool
{
public interface IPooledObjectPolicy<T>
{
T Create();
bool Return(T obj);
}
}

View File

@ -0,0 +1,69 @@
// 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.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.ObjectPool
{
public class LeakTrackingObjectPool<T> : ObjectPool<T> where T : class
{
private readonly ConditionalWeakTable<T, Tracker> _trackers = new ConditionalWeakTable<T, Tracker>();
private readonly ObjectPool<T> _inner;
public LeakTrackingObjectPool(ObjectPool<T> inner)
{
if (inner == null)
{
throw new ArgumentNullException(nameof(inner));
}
_inner = inner;
}
public override T Get()
{
var value = _inner.Get();
_trackers.Add(value, new Tracker());
return value;
}
public override void Return(T obj)
{
Tracker tracker;
if (_trackers.TryGetValue(obj, out tracker))
{
_trackers.Remove(obj);
tracker.Dispose();
}
_inner.Return(obj);
}
private class Tracker : IDisposable
{
private readonly string _stack;
private bool _disposed;
public Tracker()
{
_stack = Environment.StackTrace;
}
public void Dispose()
{
_disposed = true;
GC.SuppressFinalize(this);
}
~Tracker()
{
if (!_disposed && !Environment.HasShutdownStarted)
{
Debug.Fail($"{typeof(T).Name} was leaked. Created at: {Environment.NewLine}{_stack}");
}
}
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.ObjectPool
{
public class LeakTrackingObjectPoolProvider : ObjectPoolProvider
{
private readonly ObjectPoolProvider _inner;
public LeakTrackingObjectPoolProvider(ObjectPoolProvider inner)
{
if (inner == null)
{
throw new ArgumentNullException(nameof(inner));
}
_inner = inner;
}
public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy)
{
var inner = _inner.Create<T>(policy);
return new LeakTrackingObjectPool<T>(inner);
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A simple object pool implementation.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>pooling</PackageTags>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.ObjectPool
{
public abstract class ObjectPool<T> where T : class
{
public abstract T Get();
public abstract void Return(T obj);
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.ObjectPool
{
public abstract class ObjectPoolProvider
{
public ObjectPool<T> Create<T>() where T : class, new()
{
return Create<T>(new DefaultPooledObjectPolicy<T>());
}
public abstract ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T : class;
}
}

View File

@ -0,0 +1,29 @@
// 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.Text;
namespace Microsoft.Extensions.ObjectPool
{
public static class ObjectPoolProviderExtensions
{
public static ObjectPool<StringBuilder> CreateStringBuilderPool(this ObjectPoolProvider provider)
{
return provider.Create<StringBuilder>(new StringBuilderPooledObjectPolicy());
}
public static ObjectPool<StringBuilder> CreateStringBuilderPool(
this ObjectPoolProvider provider,
int initialCapacity,
int maximumRetainedCapacity)
{
var policy = new StringBuilderPooledObjectPolicy()
{
InitialCapacity = initialCapacity,
MaximumRetainedCapacity = maximumRetainedCapacity,
};
return provider.Create<StringBuilder>(policy);
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.ObjectPool
{
public abstract class PooledObjectPolicy<T> : IPooledObjectPolicy<T>
{
public abstract T Create();
public abstract bool Return(T obj);
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.Extensions.ObjectPool
{
public class StringBuilderPooledObjectPolicy : PooledObjectPolicy<StringBuilder>
{
public int InitialCapacity { get; set; } = 100;
public int MaximumRetainedCapacity { get; set; } = 4 * 1024;
public override StringBuilder Create()
{
return new StringBuilder(InitialCapacity);
}
public override bool Return(StringBuilder obj)
{
if (obj.Capacity > MaximumRetainedCapacity)
{
// Too big. Discard this one.
return false;
}
obj.Clear();
return true;
}
}
}

View File

@ -0,0 +1,612 @@
{
"AssemblyIdentity": "Microsoft.Extensions.ObjectPool, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPool<T0>",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Get",
"Parameters": [],
"ReturnType": "T0",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Void",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "policy",
"Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "policy",
"Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
},
{
"Name": "maximumRetained",
"Type": "System.Int32"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPoolProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_MaximumRetained",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaximumRetained",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create<T0>",
"Parameters": [
{
"Name": "policy",
"Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
}
],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<T0>",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy<T0>",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [],
"ReturnType": "T0",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Boolean",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"New": true,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [],
"ReturnType": "T0",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Boolean",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPool<T0>",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Get",
"Parameters": [],
"ReturnType": "T0",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Void",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "inner",
"Type": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPoolProvider",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create<T0>",
"Parameters": [
{
"Name": "policy",
"Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
}
],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "inner",
"Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Get",
"Parameters": [],
"ReturnType": "T0",
"Virtual": true,
"Abstract": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Void",
"Virtual": true,
"Abstract": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Protected",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create<T0>",
"Parameters": [
{
"Name": "policy",
"Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
}
],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"Virtual": true,
"Abstract": true,
"Visibility": "Public",
"GenericParameter": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Kind": "Method",
"Name": "Create<T0>",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<T0>",
"Visibility": "Public",
"GenericParameter": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"New": true,
"Class": true,
"BaseTypeOrInterfaces": []
}
]
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Protected",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProviderExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "CreateStringBuilderPool",
"Parameters": [
{
"Name": "provider",
"Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider"
}
],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<System.Text.StringBuilder>",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CreateStringBuilderPool",
"Parameters": [
{
"Name": "provider",
"Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider"
},
{
"Name": "initialCapacity",
"Type": "System.Int32"
},
{
"Name": "maximumRetainedCapacity",
"Type": "System.Int32"
}
],
"ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool<System.Text.StringBuilder>",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy<T0>",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"ImplementedInterfaces": [
"Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>"
],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [],
"ReturnType": "T0",
"Virtual": true,
"Abstract": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "T0"
}
],
"ReturnType": "System.Boolean",
"Virtual": true,
"Abstract": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<T0>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Protected",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.ObjectPool.StringBuilderPooledObjectPolicy",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy<System.Text.StringBuilder>",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [],
"ReturnType": "System.Text.StringBuilder",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<System.Text.StringBuilder>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Return",
"Parameters": [
{
"Name": "obj",
"Type": "System.Text.StringBuilder"
}
],
"ReturnType": "System.Boolean",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy<System.Text.StringBuilder>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_InitialCapacity",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_InitialCapacity",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_MaximumRetainedCapacity",
"Parameters": [],
"ReturnType": "System.Int32",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_MaximumRetainedCapacity",
"Parameters": [
{
"Name": "value",
"Type": "System.Int32"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,87 @@
// 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
{
public class DefaultObjectPoolTest
{
[Fact]
public void DefaultObjectPoolWithDefaultPolicy_GetAnd_ReturnObject_SameInstance()
{
// Arrange
var pool = new DefaultObjectPool<object>(new DefaultPooledObjectPolicy<object>());
var obj1 = pool.Get();
pool.Return(obj1);
// Act
var obj2 = pool.Get();
// Assert
Assert.Same(obj1, obj2);
}
[Fact]
public void DefaultObjectPool_GetAndReturnObject_SameInstance()
{
// Arrange
var pool = new DefaultObjectPool<List<int>>(new ListPolicy());
var list1 = pool.Get();
pool.Return(list1);
// Act
var list2 = pool.Get();
// Assert
Assert.Same(list1, list2);
}
[Fact]
public void DefaultObjectPool_CreatedByPolicy()
{
// Arrange
var pool = new DefaultObjectPool<List<int>>(new ListPolicy());
// Act
var list = pool.Get();
// Assert
Assert.Equal(17, list.Capacity);
}
[Fact]
public void DefaultObjectPool_Return_RejectedByPolicy()
{
// Arrange
var pool = new DefaultObjectPool<List<int>>(new ListPolicy());
var list1 = pool.Get();
list1.Capacity = 20;
// Act
pool.Return(list1);
var list2 = pool.Get();
// Assert
Assert.NotSame(list1, list2);
}
private class ListPolicy : IPooledObjectPolicy<List<int>>
{
public List<int> Create()
{
return new List<int>(17);
}
public bool Return(List<int> obj)
{
return obj.Capacity == 17;
}
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\src\Microsoft.Extensions.ObjectPool.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
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(Type configType) : this(configType, typeof(DefaultCoreValidationConfig))
{
}
public AspNetCoreBenchmarkAttribute(Type configType, Type validationConfigType)
{
ConfigType = configType;
ValidationConfigType = validationConfigType;
}
public IConfig Config => (IConfig) Activator.CreateInstance(UseValidationConfig ? ValidationConfigType : ConfigType, Array.Empty<object>());
}
}

View File

@ -0,0 +1,36 @@
// 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.Toolchains.CsProj;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Validators;
namespace BenchmarkDotNet.Attributes
{
internal class DefaultCoreConfig : ManualConfig
{
public DefaultCoreConfig()
{
Add(ConsoleLogger.Default);
Add(MarkdownExporter.GitHub);
Add(MemoryDiagnoser.Default);
Add(StatisticColumn.OperationsPerSecond);
Add(DefaultColumnProviders.Instance);
Add(JitOptimizationsValidator.FailOnError);
Add(Job.Core
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
.With(new GcMode { Server = true })
.With(RunStrategy.Throughput));
}
}
}

View File

@ -0,0 +1,25 @@
// 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;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Toolchains.InProcess;
namespace BenchmarkDotNet.Attributes
{
internal class DefaultCoreValidationConfig : ManualConfig
{
public DefaultCoreValidationConfig()
{
Add(ConsoleLogger.Default);
Add(Job.Dry.With(InProcessToolchain.Instance));
}
}
}

View File

@ -0,0 +1,8 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<PackageId>Microsoft.Extensions.$(ProjectDirName).Sources</PackageId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace BenchmarkDotNet.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)]
internal class ParameterizedJobConfigAttribute: AspNetCoreBenchmarkAttribute
{
public ParameterizedJobConfigAttribute(Type configType) : base(configType)
{
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess;
namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner
{
partial class Program
{
private static TextWriter _standardOutput;
private static StringBuilder _standardOutputText;
static partial void BeforeMain(string[] args);
private static int Main(string[] args)
{
BeforeMain(args);
CheckValidate(ref args);
var summaries = BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, ManualConfig.CreateEmpty());
foreach (var summary in summaries)
{
if (summary.HasCriticalValidationErrors)
{
return Fail(summary, nameof(summary.HasCriticalValidationErrors));
}
foreach (var report in summary.Reports)
{
if (!report.BuildResult.IsGenerateSuccess)
{
return Fail(report, nameof(report.BuildResult.IsGenerateSuccess));
}
if (!report.BuildResult.IsBuildSuccess)
{
return Fail(report, nameof(report.BuildResult.IsBuildSuccess));
}
if (!report.AllMeasurements.Any())
{
return Fail(report, nameof(report.AllMeasurements));
}
}
}
return 0;
}
private static int Fail(object o, string message)
{
_standardOutput?.WriteLine(_standardOutputText.ToString());
Console.Error.WriteLine("'{0}' failed, reason: '{1}'", o, message);
return 1;
}
private static void CheckValidate(ref string[] args)
{
var argsList = args.ToList();
if (argsList.Remove("--validate") || argsList.Remove("--validate-fast"))
{
SuppressConsole();
AspNetCoreBenchmarkAttribute.UseValidationConfig = true;
}
args = argsList.ToArray();
}
private static void SuppressConsole()
{
_standardOutput = Console.Out;
_standardOutputText = new StringBuilder();
Console.SetOut(new StringWriter(_standardOutputText));
}
}
}

View File

@ -0,0 +1,720 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Certificates.Generation
{
internal class CertificateManager
{
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);
private const string CertificateSubjectRegex = "CN=(.*[^,]+).*";
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
private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain;
#endif
private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)";
private const string MacOSRemoveCertificateTrustCommandLine = "sudo";
private const string MacOSRemoveCertificateTrustCommandLineArgumentsFormat = "security remove-trusted-cert -d {0}";
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
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " ";
#endif
private const int UserCancelledErrorCode = 1223;
public IList<X509Certificate2> ListCertificates(
CertificatePurpose purpose,
StoreName storeName,
StoreLocation location,
bool isValid,
bool requireExportable = true)
{
var certificates = new List<X509Certificate2>();
try
{
using (var store = new X509Store(storeName, location))
{
store.Open(OpenFlags.ReadOnly);
certificates.AddRange(store.Certificates.OfType<X509Certificate2>());
IEnumerable<X509Certificate2> matchingCertificates = certificates;
switch (purpose)
{
case CertificatePurpose.All:
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid));
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;
}
if (isValid)
{
// Ensure the certificate hasn't expired, has a private key and its exportable
// (for container/unix scenarios).
var now = DateTimeOffset.Now;
matchingCertificates = matchingCertificates
.Where(c => c.NotBefore <= now &&
now <= c.NotAfter &&
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c)));
}
// We need to enumerate the certificates early to prevent dispoisng issues.
matchingCertificates = matchingCertificates.ToList();
var certificatesToDispose = certificates.Except(matchingCertificates);
DisposeCertificates(certificatesToDispose);
store.Close();
return (IList<X509Certificate2>)matchingCertificates;
}
}
catch
{
DisposeCertificates(certificates);
certificates.Clear();
return certificates;
}
bool HasOid(X509Certificate2 certificate, string oid) =>
certificate.Extensions.OfType<X509Extension>()
.Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal));
#if !XPLAT
bool IsExportable(X509Certificate2 c) =>
((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey &&
rsaPrivateKey.CspKeyContainerInfo.Exportable) ||
(c.GetRSAPrivateKey() is RSACng cngPrivateKey &&
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
// 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.
bool IsExportable(X509Certificate2 c) =>
((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey &&
rsaPrivateKey.CspKeyContainerInfo.Exportable) || !(c.GetRSAPrivateKey() is RSACryptoServiceProvider));
#endif
}
private void DisposeCertificates(IEnumerable<X509Certificate2> disposables)
{
foreach (var disposable in disposables)
{
try
{
disposable.Dispose();
}
catch
{
}
}
}
#if NETCOREAPP2_0 || NETCOREAPP2_1
public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride)
{
var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName);
var extensions = new List<X509Extension>();
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName(LocalhostHttpsDnsName);
var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, 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 aspNetHttpsExtension = new X509Extension(
new AsnEncodedData(
new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName),
Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)),
critical: false);
extensions.Add(basicConstraints);
extensions.Add(keyUsage);
extensions.Add(enhancedKeyUsage);
extensions.Add(sanBuilder.Build(critical: true));
extensions.Add(aspNetHttpsExtension);
var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
certificate.FriendlyName = AspNetHttpsOidFriendlyName;
}
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,
DateTimeOffset notBefore,
DateTimeOffset notAfter)
{
var key = CreateKeyMaterial(RSAMinimumKeySizeInBits);
var request = new CertificateRequest(subject, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
foreach (var extension in extensions)
{
request.CertificateExtensions.Add(extension);
}
return request.CreateSelfSigned(notBefore, notAfter);
RSA CreateKeyMaterial(int minimumKeySize)
{
var rsa = RSA.Create(minimumKeySize);
if (rsa.KeySize < minimumKeySize)
{
throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits");
}
return rsa;
}
}
public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location)
{
var imported = certificate;
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// On non OSX systems we need to export the certificate and import it so that the transient
// key that we generated gets persisted.
var export = certificate.Export(X509ContentType.Pkcs12, "");
imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
Array.Clear(export, 0, export.Length);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
imported.FriendlyName = certificate.FriendlyName;
}
using (var store = new X509Store(name, location))
{
store.Open(OpenFlags.ReadWrite);
store.Add(imported);
store.Close();
};
return imported;
}
public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password)
{
if (Path.GetDirectoryName(path) != "")
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
if (includePrivateKey)
{
var bytes = certificate.Export(X509ContentType.Pkcs12, password);
try
{
File.WriteAllBytes(path, bytes);
}
finally
{
Array.Clear(bytes, 0, bytes.Length);
}
}
else
{
var bytes = certificate.Export(X509ContentType.Cert);
File.WriteAllBytes(path, bytes);
}
}
public void TrustCertificate(X509Certificate2 certificate)
{
// Strip certificate of the private key if any.
var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert));
if (!IsTrusted(publicCertificate))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
TrustCertificateOnWindows(certificate, publicCertificate);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
TrustCertificateOnMac(publicCertificate);
}
}
}
private void TrustCertificateOnMac(X509Certificate2 publicCertificate)
{
var tmpFile = Path.GetTempFileName();
try
{
ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null);
using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile))
{
process.WaitForExit();
if (process.ExitCode != 0)
{
throw new InvalidOperationException("There was an error trusting the certificate.");
}
}
}
finally
{
try
{
if (File.Exists(tmpFile))
{
File.Delete(tmpFile);
}
}
catch
{
// We don't care if we can't delete the temp file.
}
}
}
private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate)
{
publicCertificate.FriendlyName = certificate.FriendlyName;
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
try
{
store.Add(publicCertificate);
}
catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode)
{
throw new UserCancelledTrustException();
}
store.Close();
};
}
public bool IsTrusted(X509Certificate2 certificate)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: true, requireExportable: false)
.Any(c => c.Thumbprint == certificate.Thumbprint);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var subjectMatch = Regex.Match(certificate.Subject, CertificateSubjectRegex, RegexOptions.Singleline, MaxRegexTimeout);
if (!subjectMatch.Success)
{
throw new InvalidOperationException($"Can't determine the subject for the certificate with subject '{certificate.Subject}'.");
}
var subject = subjectMatch.Groups[1].Value;
using (var checkTrustProcess = Process.Start(new ProcessStartInfo(
MacOSFindCertificateCommandLine,
string.Format(MacOSFindCertificateCommandLineArgumentsFormat, subject))
{
RedirectStandardOutput = true
}))
{
var output = checkTrustProcess.StandardOutput.ReadToEnd();
checkTrustProcess.WaitForExit();
var matches = Regex.Matches(output, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, MaxRegexTimeout);
var hashes = matches.OfType<Match>().Select(m => m.Groups[1].Value).ToList();
return hashes.Any(h => string.Equals(h, certificate.Thumbprint, StringComparison.Ordinal));
}
}
else
{
return false;
}
}
public void CleanupHttpsCertificates(string subject = LocalhostHttpsDistinguishedName)
{
CleanupCertificates(CertificatePurpose.HTTPS, subject);
}
public void CleanupCertificates(CertificatePurpose purpose, string subject)
{
// 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);
foreach (var certificate in certificates)
{
RemoveCertificate(certificate, RemoveLocations.All);
}
}
public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null)
{
var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false) :
ListCertificates(purpose, storeName, storeLocation, isValid: false);
var certificatesWithName = subject == null ? certificates : certificates.Where(c => c.Subject == subject);
var removeLocation = storeName == StoreName.My ? RemoveLocations.Local : RemoveLocations.Trusted;
foreach (var certificate in certificates)
{
RemoveCertificate(certificate, removeLocation);
}
DisposeCertificates(certificates);
}
private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations)
{
switch (locations)
{
case RemoveLocations.Undefined:
throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location.");
case RemoveLocations.Local:
RemoveCertificateFromUserStore(certificate);
break;
case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux):
RemoveCertificateFromTrustedRoots(certificate);
break;
case RemoveLocations.All:
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
RemoveCertificateFromTrustedRoots(certificate);
}
RemoveCertificateFromUserStore(certificate);
break;
default:
throw new InvalidOperationException("Invalid location.");
}
}
private static void RemoveCertificateFromUserStore(X509Certificate2 certificate)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var matching = store.Certificates
.OfType<X509Certificate2>()
.Single(c => c.SerialNumber == certificate.SerialNumber);
store.Remove(matching);
store.Close();
}
}
private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var matching = store.Certificates
.OfType<X509Certificate2>()
.Single(c => c.SerialNumber == certificate.SerialNumber);
store.Remove(matching);
store.Close();
}
}
else
{
if (IsTrusted(certificate)) // On OSX this check just ensures its on the system keychain
{
try
{
RemoveCertificateTrustRule(certificate);
}
catch
{
// 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
// trusted.
}
RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate);
}
}
}
private static void RemoveCertificateTrustRule(X509Certificate2 certificate)
{
var certificatePath = Path.GetTempFileName();
try
{
var certBytes = certificate.Export(X509ContentType.Cert);
File.WriteAllBytes(certificatePath, certBytes);
var processInfo = new ProcessStartInfo(
MacOSRemoveCertificateTrustCommandLine,
string.Format(
MacOSRemoveCertificateTrustCommandLineArgumentsFormat,
certificatePath
));
using (var process = Process.Start(processInfo))
{
process.WaitForExit();
}
}
finally
{
try
{
if (File.Exists(certificatePath))
{
File.Delete(certificatePath);
}
}
catch
{
// We don't care about failing to do clean-up on a temp file.
}
}
}
private static void RemoveCertificateFromKeyChain(string keyChain, X509Certificate2 certificate)
{
var processInfo = new ProcessStartInfo(
MacOSDeleteCertificateCommandLine,
string.Format(
MacOSDeleteCertificateCommandLineArgumentsFormat,
certificate.Thumbprint.ToUpperInvariant(),
keyChain
))
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var process = Process.Start(processInfo))
{
var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
process.WaitForExit();
if (process.ExitCode != 0)
{
throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'.
{output}");
}
}
}
public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
DateTimeOffset notBefore,
DateTimeOffset notAfter,
string path = null,
bool trust = false,
bool includePrivateKey = false,
string password = null,
string subject = LocalhostHttpsDistinguishedName)
{
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,
CertificatePurpose purpose,
string path = null,
bool trust = false,
bool includePrivateKey = false,
string password = null,
string subjectOverride = null)
{
if (purpose == CertificatePurpose.All)
{
throw new ArgumentException("The certificate must have a specific purpose.");
}
var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true).Concat(
ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true));
certificates = subjectOverride == null ? certificates : certificates.Where(c => c.Subject == subjectOverride);
var result = EnsureCertificateResult.Succeeded;
X509Certificate2 certificate = null;
if (certificates.Count() > 0)
{
certificate = certificates.FirstOrDefault();
result = EnsureCertificateResult.ValidCertificatePresent;
}
else
{
try
{
switch (purpose)
{
case CertificatePurpose.All:
throw new InvalidOperationException("The certificate must have a specific purpose.");
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.");
}
}
catch
{
return EnsureCertificateResult.ErrorCreatingTheCertificate;
}
try
{
certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser);
}
catch
{
return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
}
}
if (path != null)
{
try
{
ExportCertificate(certificate, path, includePrivateKey, password);
}
catch
{
return EnsureCertificateResult.ErrorExportingTheCertificate;
}
}
if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust)
{
try
{
TrustCertificate(certificate);
}
catch (UserCancelledTrustException)
{
return EnsureCertificateResult.UserCancelledTrustStep;
}
catch
{
return EnsureCertificateResult.FailedToTrustTheCertificate;
}
}
return result;
}
private class UserCancelledTrustException : Exception
{
}
private enum RemoveLocations
{
Undefined,
Local,
Trusted,
All
}
#endif
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Certificates.Generation
{
internal enum CertificatePurpose
{
All,
HTTPS,
Signing
}
}

View File

@ -0,0 +1,8 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<PackageId>Microsoft.AspNetCore.Certificates.Generation.Sources</PackageId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NETCOREAPP2_0 || NETCOREAPP2_1
namespace Microsoft.AspNetCore.Certificates.Generation
{
internal enum EnsureCertificateResult
{
Succeeded = 1,
ValidCertificatePresent,
ErrorCreatingTheCertificate,
ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore,
ErrorExportingTheCertificate,
FailedToTrustTheCertificate,
UserCancelledTrustStep
}
}
#endif

View File

@ -0,0 +1,106 @@
// 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;
using System.Reflection;
namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Helper related to generic interface definitions and implementing classes.
/// </summary>
internal static class ClosedGenericMatcher
{
/// <summary>
/// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
/// created from <paramref name="interfaceType"/>.
/// </summary>
/// <param name="queryType">The <see cref="Type"/> of interest.</param>
/// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
/// <returns>
/// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
/// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
/// relationship.
/// </returns>
/// <remarks>
/// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
/// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
/// <c>typeof(KeyValuePair{string, object})</c>.
/// </remarks>
public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
{
if (queryType == null)
{
throw new ArgumentNullException(nameof(queryType));
}
if (interfaceType == null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
if (IsGenericInstantiation(queryType, interfaceType))
{
// queryType matches (i.e. is a closed generic type created from) the open generic type.
return queryType;
}
// Otherwise check all interfaces the type implements for a match.
// - If multiple different generic instantiations exists, we want the most derived one.
// - If that doesn't break the tie, then we sort alphabetically so that it's deterministic.
//
// We do this by looking at interfaces on the type, and recursing to the base type
// if we don't find any matches.
return GetGenericInstantiation(queryType, interfaceType);
}
private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
{
return
candidate.GetTypeInfo().IsGenericType &&
candidate.GetGenericTypeDefinition() == interfaceType;
}
private static Type GetGenericInstantiation(Type queryType, Type interfaceType)
{
Type bestMatch = null;
var interfaces = queryType.GetInterfaces();
foreach (var @interface in interfaces)
{
if (IsGenericInstantiation(@interface, interfaceType))
{
if (bestMatch == null)
{
bestMatch = @interface;
}
else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0)
{
bestMatch = @interface;
}
else
{
// There are two matches at this level of the class hierarchy, but @interface is after
// bestMatch in the sort order.
}
}
}
if (bestMatch != null)
{
return bestMatch;
}
// BaseType will be null for object and interfaces, which means we've reached 'bottom'.
var baseType = queryType?.GetTypeInfo().BaseType;
if (baseType == null)
{
return null;
}
else
{
return GetGenericInstantiation(baseType, interfaceType);
}
}
}
}

View File

@ -0,0 +1,143 @@
// 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.Extensions.CommandLineUtils
{
internal class AnsiConsole
{
private AnsiConsole(TextWriter writer, bool useConsoleColor)
{
Writer = writer;
_useConsoleColor = useConsoleColor;
if (_useConsoleColor)
{
OriginalForegroundColor = Console.ForegroundColor;
}
}
private int _boldRecursion;
private bool _useConsoleColor;
public static AnsiConsole GetOutput(bool useConsoleColor)
{
return new AnsiConsole(Console.Out, useConsoleColor);
}
public static AnsiConsole GetError(bool useConsoleColor)
{
return new AnsiConsole(Console.Error, useConsoleColor);
}
public TextWriter Writer { get; }
public ConsoleColor OriginalForegroundColor { get; }
private void SetColor(ConsoleColor color)
{
Console.ForegroundColor = (ConsoleColor)(((int)Console.ForegroundColor & 0x08) | ((int)color & 0x07));
}
private void SetBold(bool bold)
{
_boldRecursion += bold ? 1 : -1;
if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold))
{
return;
}
Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor ^ 0x08);
}
public void WriteLine(string message)
{
if (!_useConsoleColor)
{
Writer.WriteLine(message);
return;
}
var escapeScan = 0;
for (; ;)
{
var escapeIndex = message.IndexOf("\x1b[", escapeScan);
if (escapeIndex == -1)
{
var text = message.Substring(escapeScan);
Writer.Write(text);
break;
}
else
{
var startIndex = escapeIndex + 2;
var endIndex = startIndex;
while (endIndex != message.Length &&
message[endIndex] >= 0x20 &&
message[endIndex] <= 0x3f)
{
endIndex += 1;
}
var text = message.Substring(escapeScan, escapeIndex - escapeScan);
Writer.Write(text);
if (endIndex == message.Length)
{
break;
}
switch (message[endIndex])
{
case 'm':
int value;
if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out value))
{
switch (value)
{
case 1:
SetBold(true);
break;
case 22:
SetBold(false);
break;
case 30:
SetColor(ConsoleColor.Black);
break;
case 31:
SetColor(ConsoleColor.Red);
break;
case 32:
SetColor(ConsoleColor.Green);
break;
case 33:
SetColor(ConsoleColor.Yellow);
break;
case 34:
SetColor(ConsoleColor.Blue);
break;
case 35:
SetColor(ConsoleColor.Magenta);
break;
case 36:
SetColor(ConsoleColor.Cyan);
break;
case 37:
SetColor(ConsoleColor.Gray);
break;
case 39:
SetColor(OriginalForegroundColor);
break;
}
}
break;
}
escapeScan = endIndex + 1;
}
}
Writer.WriteLine();
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.CommandLineUtils
{
internal class CommandArgument
{
public CommandArgument()
{
Values = new List<string>();
}
public string Name { get; set; }
public bool ShowInHelpText { get; set; } = true;
public string Description { get; set; }
public List<string> Values { get; private set; }
public bool MultipleValues { get; set; }
public string Value
{
get
{
return Values.FirstOrDefault();
}
}
}
}

View File

@ -0,0 +1,555 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Extensions.CommandLineUtils
{
internal class CommandLineApplication
{
// Indicates whether the parser should throw an exception when it runs into an unexpected argument.
// If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all
// remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property.
private readonly bool _throwOnUnexpectedArg;
public CommandLineApplication(bool throwOnUnexpectedArg = true)
{
_throwOnUnexpectedArg = throwOnUnexpectedArg;
Options = new List<CommandOption>();
Arguments = new List<CommandArgument>();
Commands = new List<CommandLineApplication>();
RemainingArguments = new List<string>();
Invoke = () => 0;
}
public CommandLineApplication Parent { get; set; }
public string Name { get; set; }
public string FullName { get; set; }
public string Syntax { get; set; }
public string Description { get; set; }
public bool ShowInHelpText { get; set; } = true;
public string ExtendedHelpText { get; set; }
public readonly List<CommandOption> Options;
public CommandOption OptionHelp { get; private set; }
public CommandOption OptionVersion { get; private set; }
public readonly List<CommandArgument> Arguments;
public readonly List<string> RemainingArguments;
public bool IsShowingInformation { get; protected set; } // Is showing help or version?
public Func<int> Invoke { get; set; }
public Func<string> LongVersionGetter { get; set; }
public Func<string> ShortVersionGetter { get; set; }
public readonly List<CommandLineApplication> Commands;
public bool AllowArgumentSeparator { get; set; }
public TextWriter Out { get; set; } = Console.Out;
public TextWriter Error { get; set; } = Console.Error;
public IEnumerable<CommandOption> GetOptions()
{
var expr = Options.AsEnumerable();
var rootNode = this;
while (rootNode.Parent != null)
{
rootNode = rootNode.Parent;
expr = expr.Concat(rootNode.Options.Where(o => o.Inherited));
}
return expr;
}
public CommandLineApplication Command(string name, Action<CommandLineApplication> configuration,
bool throwOnUnexpectedArg = true)
{
var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this };
Commands.Add(command);
configuration(command);
return command;
}
public CommandOption Option(string template, string description, CommandOptionType optionType)
=> Option(template, description, optionType, _ => { }, inherited: false);
public CommandOption Option(string template, string description, CommandOptionType optionType, bool inherited)
=> Option(template, description, optionType, _ => { }, inherited);
public CommandOption Option(string template, string description, CommandOptionType optionType, Action<CommandOption> configuration)
=> Option(template, description, optionType, configuration, inherited: false);
public CommandOption Option(string template, string description, CommandOptionType optionType, Action<CommandOption> configuration, bool inherited)
{
var option = new CommandOption(template, optionType)
{
Description = description,
Inherited = inherited
};
Options.Add(option);
configuration(option);
return option;
}
public CommandArgument Argument(string name, string description, bool multipleValues = false)
{
return Argument(name, description, _ => { }, multipleValues);
}
public CommandArgument Argument(string name, string description, Action<CommandArgument> configuration, bool multipleValues = false)
{
var lastArg = Arguments.LastOrDefault();
if (lastArg != null && lastArg.MultipleValues)
{
var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.",
lastArg.Name);
throw new InvalidOperationException(message);
}
var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues };
Arguments.Add(argument);
configuration(argument);
return argument;
}
public void OnExecute(Func<int> invoke)
{
Invoke = invoke;
}
public void OnExecute(Func<Task<int>> invoke)
{
Invoke = () => invoke().Result;
}
public int Execute(params string[] args)
{
CommandLineApplication command = this;
CommandOption option = null;
IEnumerator<CommandArgument> arguments = null;
for (var index = 0; index < args.Length; index++)
{
var arg = args[index];
var processed = false;
if (!processed && option == null)
{
string[] longOption = null;
string[] shortOption = null;
if (arg.StartsWith("--"))
{
longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2);
}
else if (arg.StartsWith("-"))
{
shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2);
}
if (longOption != null)
{
processed = true;
var longOptionName = longOption[0];
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal));
if (option == null)
{
if (string.IsNullOrEmpty(longOptionName) && !command._throwOnUnexpectedArg && AllowArgumentSeparator)
{
// skip over the '--' argument separator
index++;
}
HandleUnexpectedArg(command, args, index, argTypeName: "option");
break;
}
// If we find a help/version option, show information and stop parsing
if (command.OptionHelp == option)
{
command.ShowHelp();
return 0;
}
else if (command.OptionVersion == option)
{
command.ShowVersion();
return 0;
}
if (longOption.Length == 2)
{
if (!option.TryParse(longOption[1]))
{
command.ShowHint();
throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'");
}
option = null;
}
else if (option.OptionType == CommandOptionType.NoValue)
{
// No value is needed for this option
option.TryParse(null);
option = null;
}
}
if (shortOption != null)
{
processed = true;
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal));
// If not a short option, try symbol option
if (option == null)
{
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal));
}
if (option == null)
{
HandleUnexpectedArg(command, args, index, argTypeName: "option");
break;
}
// If we find a help/version option, show information and stop parsing
if (command.OptionHelp == option)
{
command.ShowHelp();
return 0;
}
else if (command.OptionVersion == option)
{
command.ShowVersion();
return 0;
}
if (shortOption.Length == 2)
{
if (!option.TryParse(shortOption[1]))
{
command.ShowHint();
throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'");
}
option = null;
}
else if (option.OptionType == CommandOptionType.NoValue)
{
// No value is needed for this option
option.TryParse(null);
option = null;
}
}
}
if (!processed && option != null)
{
processed = true;
if (!option.TryParse(arg))
{
command.ShowHint();
throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'");
}
option = null;
}
if (!processed && arguments == null)
{
var currentCommand = command;
foreach (var subcommand in command.Commands)
{
if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase))
{
processed = true;
command = subcommand;
break;
}
}
// If we detect a subcommand
if (command != currentCommand)
{
processed = true;
}
}
if (!processed)
{
if (arguments == null)
{
arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator());
}
if (arguments.MoveNext())
{
processed = true;
arguments.Current.Values.Add(arg);
}
}
if (!processed)
{
HandleUnexpectedArg(command, args, index, argTypeName: "command or argument");
break;
}
}
if (option != null)
{
command.ShowHint();
throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'");
}
return command.Invoke();
}
// Helper method that adds a help option
public CommandOption HelpOption(string template)
{
// Help option is special because we stop parsing once we see it
// So we store it separately for further use
OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue);
return OptionHelp;
}
public CommandOption VersionOption(string template,
string shortFormVersion,
string longFormVersion = null)
{
if (longFormVersion == null)
{
return VersionOption(template, () => shortFormVersion);
}
else
{
return VersionOption(template, () => shortFormVersion, () => longFormVersion);
}
}
// Helper method that adds a version option
public CommandOption VersionOption(string template,
Func<string> shortFormVersionGetter,
Func<string> longFormVersionGetter = null)
{
// Version option is special because we stop parsing once we see it
// So we store it separately for further use
OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue);
ShortVersionGetter = shortFormVersionGetter;
LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter;
return OptionVersion;
}
// Show short hint that reminds users to use help option
public void ShowHint()
{
if (OptionHelp != null)
{
Out.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName));
}
}
// Show full help
public void ShowHelp(string commandName = null)
{
for (var cmd = this; cmd != null; cmd = cmd.Parent)
{
cmd.IsShowingInformation = true;
}
Out.WriteLine(GetHelpText(commandName));
}
public virtual string GetHelpText(string commandName = null)
{
var headerBuilder = new StringBuilder("Usage:");
for (var cmd = this; cmd != null; cmd = cmd.Parent)
{
headerBuilder.Insert(6, string.Format(" {0}", cmd.Name));
}
CommandLineApplication target;
if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase))
{
target = this;
}
else
{
target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase));
if (target != null)
{
headerBuilder.AppendFormat(" {0}", commandName);
}
else
{
// The command name is invalid so don't try to show help for something that doesn't exist
target = this;
}
}
var optionsBuilder = new StringBuilder();
var commandsBuilder = new StringBuilder();
var argumentsBuilder = new StringBuilder();
var arguments = target.Arguments.Where(a => a.ShowInHelpText).ToList();
if (arguments.Any())
{
headerBuilder.Append(" [arguments]");
argumentsBuilder.AppendLine();
argumentsBuilder.AppendLine("Arguments:");
var maxArgLen = arguments.Max(a => a.Name.Length);
var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxArgLen + 2);
foreach (var arg in arguments)
{
argumentsBuilder.AppendFormat(outputFormat, arg.Name, arg.Description);
argumentsBuilder.AppendLine();
}
}
var options = target.GetOptions().Where(o => o.ShowInHelpText).ToList();
if (options.Any())
{
headerBuilder.Append(" [options]");
optionsBuilder.AppendLine();
optionsBuilder.AppendLine("Options:");
var maxOptLen = options.Max(o => o.Template.Length);
var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2);
foreach (var opt in options)
{
optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description);
optionsBuilder.AppendLine();
}
}
var commands = target.Commands.Where(c => c.ShowInHelpText).ToList();
if (commands.Any())
{
headerBuilder.Append(" [command]");
commandsBuilder.AppendLine();
commandsBuilder.AppendLine("Commands:");
var maxCmdLen = commands.Max(c => c.Name.Length);
var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2);
foreach (var cmd in commands.OrderBy(c => c.Name))
{
commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description);
commandsBuilder.AppendLine();
}
if (OptionHelp != null)
{
commandsBuilder.AppendLine();
commandsBuilder.AppendFormat($"Use \"{target.Name} [command] --{OptionHelp.LongName}\" for more information about a command.");
commandsBuilder.AppendLine();
}
}
if (target.AllowArgumentSeparator)
{
headerBuilder.Append(" [[--] <arg>...]");
}
headerBuilder.AppendLine();
var nameAndVersion = new StringBuilder();
nameAndVersion.AppendLine(GetFullNameAndVersion());
nameAndVersion.AppendLine();
return nameAndVersion.ToString()
+ headerBuilder.ToString()
+ argumentsBuilder.ToString()
+ optionsBuilder.ToString()
+ commandsBuilder.ToString()
+ target.ExtendedHelpText;
}
public void ShowVersion()
{
for (var cmd = this; cmd != null; cmd = cmd.Parent)
{
cmd.IsShowingInformation = true;
}
Out.WriteLine(FullName);
Out.WriteLine(LongVersionGetter());
}
public string GetFullNameAndVersion()
{
return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter());
}
public void ShowRootCommandFullNameAndVersion()
{
var rootCmd = this;
while (rootCmd.Parent != null)
{
rootCmd = rootCmd.Parent;
}
Out.WriteLine(rootCmd.GetFullNameAndVersion());
Out.WriteLine();
}
private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName)
{
if (command._throwOnUnexpectedArg)
{
command.ShowHint();
throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'");
}
else
{
// All remaining arguments are stored for further use
command.RemainingArguments.AddRange(new ArraySegment<string>(args, index, args.Length - index));
}
}
private class CommandArgumentEnumerator : IEnumerator<CommandArgument>
{
private readonly IEnumerator<CommandArgument> _enumerator;
public CommandArgumentEnumerator(IEnumerator<CommandArgument> enumerator)
{
_enumerator = enumerator;
}
public CommandArgument Current
{
get
{
return _enumerator.Current;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
_enumerator.Dispose();
}
public bool MoveNext()
{
if (Current == null || !Current.MultipleValues)
{
return _enumerator.MoveNext();
}
// If current argument allows multiple values, we don't move forward and
// all later values will be added to current CommandArgument.Values
return true;
}
public void Reset()
{
_enumerator.Reset();
}
}
}
}

View File

@ -0,0 +1,108 @@
// 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;
namespace Microsoft.Extensions.CommandLineUtils
{
internal class CommandOption
{
public CommandOption(string template, CommandOptionType optionType)
{
Template = template;
OptionType = optionType;
Values = new List<string>();
foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries))
{
if (part.StartsWith("--"))
{
LongName = part.Substring(2);
}
else if (part.StartsWith("-"))
{
var optName = part.Substring(1);
// If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?")
if (optName.Length == 1 && !IsEnglishLetter(optName[0]))
{
SymbolName = optName;
}
else
{
ShortName = optName;
}
}
else if (part.StartsWith("<") && part.EndsWith(">"))
{
ValueName = part.Substring(1, part.Length - 2);
}
else
{
throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
}
}
if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName))
{
throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
}
}
public string Template { get; set; }
public string ShortName { get; set; }
public string LongName { get; set; }
public string SymbolName { get; set; }
public string ValueName { get; set; }
public string Description { get; set; }
public List<string> Values { get; private set; }
public CommandOptionType OptionType { get; private set; }
public bool ShowInHelpText { get; set; } = true;
public bool Inherited { get; set; }
public bool TryParse(string value)
{
switch (OptionType)
{
case CommandOptionType.MultipleValue:
Values.Add(value);
break;
case CommandOptionType.SingleValue:
if (Values.Any())
{
return false;
}
Values.Add(value);
break;
case CommandOptionType.NoValue:
if (value != null)
{
return false;
}
// Add a value to indicate that this option was specified
Values.Add("on");
break;
default:
break;
}
return true;
}
public bool HasValue()
{
return Values.Any();
}
public string Value()
{
return HasValue() ? Values[0] : null;
}
private bool IsEnglishLetter(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.CommandLineUtils
{
internal enum CommandOptionType
{
MultipleValue,
SingleValue,
NoValue
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.CommandLineUtils
{
internal class CommandParsingException : Exception
{
public CommandParsingException(CommandLineApplication command, string message)
: base(message)
{
Command = command;
}
public CommandLineApplication Command { get; }
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>00947d4a-c20e-46e3-90c3-6cd6bc87ee72</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="Microsoft.Extensions.CommandLineUtils.Sources.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Extensions.CommandLineUtils
{
/// <summary>
/// A utility for escaping arguments for new processes.
/// </summary>
internal static class ArgumentEscaper
{
/// <summary>
/// Undo the processing which took place to create string[] args in Main, so that the next process will
/// receive the same string[] args.
/// </summary>
/// <remarks>
/// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
/// </remarks>
/// <param name="args"></param>
/// <returns></returns>
public static string EscapeAndConcatenate(IEnumerable<string> args)
=> string.Join(" ", args.Select(EscapeSingleArg));
private static string EscapeSingleArg(string arg)
{
var sb = new StringBuilder();
var needsQuotes = ShouldSurroundWithQuotes(arg);
var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg);
if (needsQuotes)
{
sb.Append('"');
}
for (int i = 0; i < arg.Length; ++i)
{
var backslashes = 0;
// Consume all backslashes
while (i < arg.Length && arg[i] == '\\')
{
backslashes++;
i++;
}
if (i == arg.Length && isQuoted)
{
// Escape any backslashes at the end of the arg when the argument is also quoted.
// This ensures the outside quote is interpreted as an argument delimiter
sb.Append('\\', 2 * backslashes);
}
else if (i == arg.Length)
{
// At then end of the arg, which isn't quoted,
// just add the backslashes, no need to escape
sb.Append('\\', backslashes);
}
else if (arg[i] == '"')
{
// Escape any preceding backslashes and the quote
sb.Append('\\', (2 * backslashes) + 1);
sb.Append('"');
}
else
{
// Output any consumed backslashes and the character
sb.Append('\\', backslashes);
sb.Append(arg[i]);
}
}
if (needsQuotes)
{
sb.Append('"');
}
return sb.ToString();
}
private static bool ShouldSurroundWithQuotes(string argument)
{
// Don't quote already quoted strings
if (IsSurroundedWithQuotes(argument))
{
return false;
}
// Only quote if whitespace exists in the string
return ContainsWhitespace(argument);
}
private static bool IsSurroundedWithQuotes(string argument)
{
if (argument.Length <= 1)
{
return false;
}
return argument[0] == '"' && argument[argument.Length - 1] == '"';
}
private static bool ContainsWhitespace(string argument)
=> argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0;
}
}

View File

@ -0,0 +1,58 @@
// 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.
// System.AppContext.GetData is not available in these frameworks
#if !NET451 && !NET452 && !NET46 && !NET461
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Extensions.CommandLineUtils
{
/// <summary>
/// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application
/// </summary>
internal static class DotNetMuxer
{
private const string MuxerName = "dotnet";
static DotNetMuxer()
{
MuxerPath = TryFindMuxerPath();
}
/// <summary>
/// The full filepath to the .NET Core muxer.
/// </summary>
public static string MuxerPath { get; }
/// <summary>
/// Finds the full filepath to the .NET Core muxer,
/// or returns a string containing the default name of the .NET Core muxer ('dotnet').
/// </summary>
/// <returns>The path or a string named 'dotnet'.</returns>
public static string MuxerPathOrDefault()
=> MuxerPath ?? MuxerName;
private static string TryFindMuxerPath()
{
var fileName = MuxerName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fileName += ".exe";
}
var mainModule = Process.GetCurrentProcess().MainModule;
if (!string.IsNullOrEmpty(mainModule?.FileName)
&& Path.GetFileName(mainModule.FileName).Equals(fileName, StringComparison.OrdinalIgnoreCase))
{
return mainModule.FileName;
}
return null;
}
}
}
#endif

View File

@ -0,0 +1,155 @@
// 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;
using System.Collections.Generic;
namespace Microsoft.Extensions.Internal
{
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _sourceDictionary;
private readonly IEqualityComparer<TKey> _comparer;
private IDictionary<TKey, TValue> _innerDictionary;
public CopyOnWriteDictionary(
IDictionary<TKey, TValue> sourceDictionary,
IEqualityComparer<TKey> comparer)
{
if (sourceDictionary == null)
{
throw new ArgumentNullException(nameof(sourceDictionary));
}
if (comparer == null)
{
throw new ArgumentNullException(nameof(comparer));
}
_sourceDictionary = sourceDictionary;
_comparer = comparer;
}
private IDictionary<TKey, TValue> ReadDictionary
{
get
{
return _innerDictionary ?? _sourceDictionary;
}
}
private IDictionary<TKey, TValue> WriteDictionary
{
get
{
if (_innerDictionary == null)
{
_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary,
_comparer);
}
return _innerDictionary;
}
}
public virtual ICollection<TKey> Keys
{
get
{
return ReadDictionary.Keys;
}
}
public virtual ICollection<TValue> Values
{
get
{
return ReadDictionary.Values;
}
}
public virtual int Count
{
get
{
return ReadDictionary.Count;
}
}
public virtual bool IsReadOnly
{
get
{
return false;
}
}
public virtual TValue this[TKey key]
{
get
{
return ReadDictionary[key];
}
set
{
WriteDictionary[key] = value;
}
}
public virtual bool ContainsKey(TKey key)
{
return ReadDictionary.ContainsKey(key);
}
public virtual void Add(TKey key, TValue value)
{
WriteDictionary.Add(key, value);
}
public virtual bool Remove(TKey key)
{
return WriteDictionary.Remove(key);
}
public virtual bool TryGetValue(TKey key, out TValue value)
{
return ReadDictionary.TryGetValue(key, out value);
}
public virtual void Add(KeyValuePair<TKey, TValue> item)
{
WriteDictionary.Add(item);
}
public virtual void Clear()
{
WriteDictionary.Clear();
}
public virtual bool Contains(KeyValuePair<TKey, TValue> item)
{
return ReadDictionary.Contains(item);
}
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
ReadDictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return WriteDictionary.Remove(item);
}
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ReadDictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,166 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.Internal
{
internal struct CopyOnWriteDictionaryHolder<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _source;
private Dictionary<TKey, TValue> _copy;
public CopyOnWriteDictionaryHolder(Dictionary<TKey, TValue> source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
_source = source;
_copy = null;
}
public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder<TKey, TValue> source)
{
_source = source._copy ?? source._source;
_copy = null;
}
public bool HasBeenCopied => _copy != null;
public Dictionary<TKey, TValue> ReadDictionary
{
get
{
if (_copy != null)
{
return _copy;
}
else if (_source != null)
{
return _source;
}
else
{
// Default-Constructor case
_copy = new Dictionary<TKey, TValue>();
return _copy;
}
}
}
public Dictionary<TKey, TValue> WriteDictionary
{
get
{
if (_copy == null && _source == null)
{
// Default-Constructor case
_copy = new Dictionary<TKey, TValue>();
}
else if (_copy == null)
{
_copy = new Dictionary<TKey, TValue>(_source, _source.Comparer);
}
return _copy;
}
}
public Dictionary<TKey, TValue>.KeyCollection Keys
{
get
{
return ReadDictionary.Keys;
}
}
public Dictionary<TKey, TValue>.ValueCollection Values
{
get
{
return ReadDictionary.Values;
}
}
public int Count
{
get
{
return ReadDictionary.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public TValue this[TKey key]
{
get
{
return ReadDictionary[key];
}
set
{
WriteDictionary[key] = value;
}
}
public bool ContainsKey(TKey key)
{
return ReadDictionary.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
WriteDictionary.Add(key, value);
}
public bool Remove(TKey key)
{
return WriteDictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return ReadDictionary.TryGetValue(key, out value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Add(item);
}
public void Clear()
{
WriteDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Remove(item);
}
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
{
return ReadDictionary.GetEnumerator();
}
}
}

View File

@ -0,0 +1,84 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal
{
internal struct HashCodeCombiner
{
private long _combinedHash64;
public int CombinedHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _combinedHash64.GetHashCode(); }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private HashCodeCombiner(long seed)
{
_combinedHash64 = seed;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(IEnumerable e)
{
if (e == null)
{
Add(0);
}
else
{
var count = 0;
foreach (object o in e)
{
Add(o);
count++;
}
Add(count);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator int(HashCodeCombiner self)
{
return self.CombinedHash;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int i)
{
_combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(string s)
{
var hashCode = (s != null) ? s.GetHashCode() : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(object o)
{
var hashCode = (o != null) ? o.GetHashCode() : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
{
var hashCode = value != null ? comparer.GetHashCode(value) : 0;
Add(hashCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static HashCodeCombiner Start()
{
return new HashCodeCombiner(0x1505L);
}
}
}

View File

@ -0,0 +1,127 @@
// 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;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal
{
internal struct AwaitableInfo
{
public Type AwaiterType { get; }
public PropertyInfo AwaiterIsCompletedProperty { get; }
public MethodInfo AwaiterGetResultMethod { get; }
public MethodInfo AwaiterOnCompletedMethod { get; }
public MethodInfo AwaiterUnsafeOnCompletedMethod { get; }
public Type ResultType { get; }
public MethodInfo GetAwaiterMethod { get; }
public AwaitableInfo(
Type awaiterType,
PropertyInfo awaiterIsCompletedProperty,
MethodInfo awaiterGetResultMethod,
MethodInfo awaiterOnCompletedMethod,
MethodInfo awaiterUnsafeOnCompletedMethod,
Type resultType,
MethodInfo getAwaiterMethod)
{
AwaiterType = awaiterType;
AwaiterIsCompletedProperty = awaiterIsCompletedProperty;
AwaiterGetResultMethod = awaiterGetResultMethod;
AwaiterOnCompletedMethod = awaiterOnCompletedMethod;
AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod;
ResultType = resultType;
GetAwaiterMethod = getAwaiterMethod;
}
public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo)
{
// Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347
// Awaitable must have method matching "object GetAwaiter()"
var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m =>
m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase)
&& m.GetParameters().Length == 0
&& m.ReturnType != null);
if (getAwaiterMethod == null)
{
awaitableInfo = default(AwaitableInfo);
return false;
}
var awaiterType = getAwaiterMethod.ReturnType;
// Awaiter must have property matching "bool IsCompleted { get; }"
var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p =>
p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase)
&& p.PropertyType == typeof(bool)
&& p.GetMethod != null);
if (isCompletedProperty == null)
{
awaitableInfo = default(AwaitableInfo);
return false;
}
// Awaiter must implement INotifyCompletion
var awaiterInterfaces = awaiterType.GetInterfaces();
var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion));
if (!implementsINotifyCompletion)
{
awaitableInfo = default(AwaitableInfo);
return false;
}
// INotifyCompletion supplies a method matching "void OnCompleted(Action action)"
var iNotifyCompletionMap = awaiterType
.GetTypeInfo()
.GetRuntimeInterfaceMap(typeof(INotifyCompletion));
var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m =>
m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase)
&& m.ReturnType == typeof(void)
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(Action));
// Awaiter optionally implements ICriticalNotifyCompletion
var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion));
MethodInfo unsafeOnCompletedMethod;
if (implementsICriticalNotifyCompletion)
{
// ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)"
var iCriticalNotifyCompletionMap = awaiterType
.GetTypeInfo()
.GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion));
unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m =>
m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase)
&& m.ReturnType == typeof(void)
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(Action));
}
else
{
unsafeOnCompletedMethod = null;
}
// Awaiter must have method matching "void GetResult" or "T GetResult()"
var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m =>
m.Name.Equals("GetResult")
&& m.GetParameters().Length == 0);
if (getResultMethod == null)
{
awaitableInfo = default(AwaitableInfo);
return false;
}
awaitableInfo = new AwaitableInfo(
awaiterType,
isCompletedProperty,
getResultMethod,
onCompletedMethod,
unsafeOnCompletedMethod,
getResultMethod.ReturnType,
getAwaiterMethod);
return true;
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq.Expressions;
namespace Microsoft.Extensions.Internal
{
internal struct CoercedAwaitableInfo
{
public AwaitableInfo AwaitableInfo { get; }
public Expression CoercerExpression { get; }
public Type CoercerResultType { get; }
public bool RequiresCoercion => CoercerExpression != null;
public CoercedAwaitableInfo(AwaitableInfo awaitableInfo)
{
AwaitableInfo = awaitableInfo;
CoercerExpression = null;
CoercerResultType = null;
}
public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo)
{
CoercerExpression = coercerExpression;
CoercerResultType = coercerResultType;
AwaitableInfo = coercedAwaitableInfo;
}
public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info)
{
if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo))
{
info = new CoercedAwaitableInfo(directlyAwaitableInfo);
return true;
}
// It's not directly awaitable, but maybe we can coerce it.
// Currently we support coercing FSharpAsync<T>.
if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type,
out var coercerExpression,
out var coercerResultType))
{
if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo))
{
info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo);
return true;
}
}
info = default(CoercedAwaitableInfo);
return false;
}
}
}

View File

@ -0,0 +1,340 @@
// 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.Expressions;
using System.Reflection;
namespace Microsoft.Extensions.Internal
{
internal class ObjectMethodExecutor
{
private readonly object[] _parameterDefaultValues;
private readonly MethodExecutorAsync _executorAsync;
private readonly MethodExecutor _executor;
private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor =
typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] {
typeof(object), // customAwaitable
typeof(Func<object, object>), // getAwaiterMethod
typeof(Func<object, bool>), // isCompletedMethod
typeof(Func<object, object>), // getResultMethod
typeof(Action<object, Action>), // onCompletedMethod
typeof(Action<object, Action>) // unsafeOnCompletedMethod
});
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues)
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo;
MethodParameters = methodInfo.GetParameters();
TargetTypeInfo = targetTypeInfo;
MethodReturnType = methodInfo.ReturnType;
var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo);
IsMethodAsync = isAwaitable;
AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null;
// Upstream code may prefer to use the sync-executor even for async methods, because if it knows
// that the result is a specific Task<T> where T is known, then it can directly cast to that type
// and await it without the extra heap allocations involved in the _executorAsync code path.
_executor = GetExecutor(methodInfo, targetTypeInfo);
if (IsMethodAsync)
{
_executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo);
}
_parameterDefaultValues = parameterDefaultValues;
}
private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters);
private delegate object MethodExecutor(object target, object[] parameters);
private delegate void VoidMethodExecutor(object target, object[] parameters);
public MethodInfo MethodInfo { get; }
public ParameterInfo[] MethodParameters { get; }
public TypeInfo TargetTypeInfo { get; }
public Type AsyncResultType { get; }
// This field is made internal set because it is set in unit tests.
public Type MethodReturnType { get; internal set; }
public bool IsMethodAsync { get; }
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null);
}
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues)
{
if (parameterDefaultValues == null)
{
throw new ArgumentNullException(nameof(parameterDefaultValues));
}
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues);
}
/// <summary>
/// Executes the configured method on <paramref name="target"/>. This can be used whether or not
/// the configured method is asynchronous.
/// </summary>
/// <remarks>
/// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than
/// ExecuteAsync if you know at compile time what the return type is, because then you can directly
/// "await" that value (via a cast), and then the generated code will be able to reference the
/// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated
/// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at
/// compile time what type it would be.
/// </remarks>
/// <param name="target">The object whose method is to be executed.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>The method return value.</returns>
public object Execute(object target, object[] parameters)
{
return _executor(target, parameters);
}
/// <summary>
/// Executes the configured method on <paramref name="target"/>. This can only be used if the configured
/// method is asynchronous.
/// </summary>
/// <remarks>
/// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync,
/// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations
/// as compared with using Execute and then using "await" on the result value typecasted to the known
/// awaitable type. The possible extra heap allocations are for:
///
/// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally
/// it's a reference type, and you normally create a new instance per call).
/// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance
/// of it, and if it is, it will have to be boxed so the calling code can reference it as an object).
/// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling
/// code doesn't know what type it's going to be).
/// </remarks>
/// <param name="target">The object whose method is to be executed.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>An object that you can "await" to get the method return value.</returns>
public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters)
{
return _executorAsync(target, parameters);
}
public object GetDefaultValueForParameter(int index)
{
if (_parameterDefaultValues == null)
{
throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied.");
}
if (index < 0 || index > MethodParameters.Length - 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _parameterDefaultValues[index];
}
private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void))
{
var lambda = Expression.Lambda<VoidMethodExecutor>(methodCall, targetParameter, parametersParameter);
var voidExecutor = lambda.Compile();
return WrapVoidMethod(voidExecutor);
}
else
{
// must coerce methodCall to match ActionExecutor signature
var castMethodCall = Expression.Convert(methodCall, typeof(object));
var lambda = Expression.Lambda<MethodExecutor>(castMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}
}
private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor)
{
return delegate (object target, object[] parameters)
{
executor(target, parameters);
return null;
};
}
private static MethodExecutorAsync GetExecutorAsync(
MethodInfo methodInfo,
TypeInfo targetTypeInfo,
CoercedAwaitableInfo coercedAwaitableInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// Using the method return value, construct an ObjectMethodExecutorAwaitable based on
// the info we have about its implementation of the awaitable pattern. Note that all
// the funcs/actions we construct here are precompiled, so that only one instance of
// each is preserved throughout the lifetime of the ObjectMethodExecutor.
// var getAwaiterFunc = (object awaitable) =>
// (object)((CustomAwaitableType)awaitable).GetAwaiter();
var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable");
var awaitableInfo = coercedAwaitableInfo.AwaitableInfo;
var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType;
var getAwaiterFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(customAwaitableParam, postCoercionMethodReturnType),
awaitableInfo.GetAwaiterMethod),
typeof(object)),
customAwaitableParam).Compile();
// var isCompletedFunc = (object awaiter) =>
// ((CustomAwaiterType)awaiter).IsCompleted;
var isCompletedParam = Expression.Parameter(typeof(object), "awaiter");
var isCompletedFunc = Expression.Lambda<Func<object, bool>>(
Expression.MakeMemberAccess(
Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterIsCompletedProperty),
isCompletedParam).Compile();
var getResultParam = Expression.Parameter(typeof(object), "awaiter");
Func<object, object> getResultFunc;
if (awaitableInfo.ResultType == typeof(void))
{
// var getResultFunc = (object awaiter) =>
// {
// ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions
// return (object)null;
// };
getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Block(
Expression.Call(
Expression.Convert(getResultParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterGetResultMethod),
Expression.Constant(null)
),
getResultParam).Compile();
}
else
{
// var getResultFunc = (object awaiter) =>
// (object)((CustomAwaiterType)awaiter).GetResult();
getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(getResultParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterGetResultMethod),
typeof(object)),
getResultParam).Compile();
}
// var onCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).OnCompleted(continuation);
// };
var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter");
var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation");
var onCompletedFunc = Expression.Lambda<Action<object, Action>>(
Expression.Call(
Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterOnCompletedMethod,
onCompletedParam2),
onCompletedParam1,
onCompletedParam2).Compile();
Action<object, Action> unsafeOnCompletedFunc = null;
if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null)
{
// var unsafeOnCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation);
// };
var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter");
var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation");
unsafeOnCompletedFunc = Expression.Lambda<Action<object, Action>>(
Expression.Call(
Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterUnsafeOnCompletedMethod,
unsafeOnCompletedParam2),
unsafeOnCompletedParam1,
unsafeOnCompletedParam2).Compile();
}
// If we need to pass the method call result through a coercer function to get an
// awaitable, then do so.
var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion
? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall)
: (Expression)methodCall;
// return new ObjectMethodExecutorAwaitable(
// (object)coercedMethodCall,
// getAwaiterFunc,
// isCompletedFunc,
// getResultFunc,
// onCompletedFunc,
// unsafeOnCompletedFunc);
var returnValueExpression = Expression.New(
_objectMethodExecutorAwaitableConstructor,
Expression.Convert(coercedMethodCall, typeof(object)),
Expression.Constant(getAwaiterFunc),
Expression.Constant(isCompletedFunc),
Expression.Constant(getResultFunc),
Expression.Constant(onCompletedFunc),
Expression.Constant(unsafeOnCompletedFunc, typeof(Action<object, Action>)));
var lambda = Expression.Lambda<MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter);
return lambda.Compile();
}
}
}

View File

@ -0,0 +1,114 @@
// 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;
namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Provides a common awaitable structure that <see cref="ObjectMethodExecutor.ExecuteAsync"/> can
/// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an
/// application-defined custom awaitable.
/// </summary>
internal struct ObjectMethodExecutorAwaitable
{
private readonly object _customAwaitable;
private readonly Func<object, object> _getAwaiterMethod;
private readonly Func<object, bool> _isCompletedMethod;
private readonly Func<object, object> _getResultMethod;
private readonly Action<object, Action> _onCompletedMethod;
private readonly Action<object, Action> _unsafeOnCompletedMethod;
// Perf note: since we're requiring the customAwaitable to be supplied here as an object,
// this will trigger a further allocation if it was a value type (i.e., to box it). We can't
// fix this by making the customAwaitable type generic, because the calling code typically
// does not know the type of the awaitable/awaiter at compile-time anyway.
//
// However, we could fix it by not passing the customAwaitable here at all, and instead
// passing a func that maps directly from the target object (e.g., controller instance),
// target method (e.g., action method info), and params array to the custom awaiter in the
// GetAwaiter() method below. In effect, by delaying the actual method call until the
// upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance.
// This optimization is not currently implemented because:
// [1] It would make no difference when the awaitable was an object type, which is
// by far the most common scenario (e.g., System.Task<T>).
// [2] It would be complex - we'd need some kind of object pool to track all the parameter
// arrays until we needed to use them in GetAwaiter().
// We can reconsider this in the future if there's a need to optimize for ValueTask<T>
// or other value-typed awaitables.
public ObjectMethodExecutorAwaitable(
object customAwaitable,
Func<object, object> getAwaiterMethod,
Func<object, bool> isCompletedMethod,
Func<object, object> getResultMethod,
Action<object, Action> onCompletedMethod,
Action<object, Action> unsafeOnCompletedMethod)
{
_customAwaitable = customAwaitable;
_getAwaiterMethod = getAwaiterMethod;
_isCompletedMethod = isCompletedMethod;
_getResultMethod = getResultMethod;
_onCompletedMethod = onCompletedMethod;
_unsafeOnCompletedMethod = unsafeOnCompletedMethod;
}
public Awaiter GetAwaiter()
{
var customAwaiter = _getAwaiterMethod(_customAwaitable);
return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod);
}
public struct Awaiter : ICriticalNotifyCompletion
{
private readonly object _customAwaiter;
private readonly Func<object, bool> _isCompletedMethod;
private readonly Func<object, object> _getResultMethod;
private readonly Action<object, Action> _onCompletedMethod;
private readonly Action<object, Action> _unsafeOnCompletedMethod;
public Awaiter(
object customAwaiter,
Func<object, bool> isCompletedMethod,
Func<object, object> getResultMethod,
Action<object, Action> onCompletedMethod,
Action<object, Action> unsafeOnCompletedMethod)
{
_customAwaiter = customAwaiter;
_isCompletedMethod = isCompletedMethod;
_getResultMethod = getResultMethod;
_onCompletedMethod = onCompletedMethod;
_unsafeOnCompletedMethod = unsafeOnCompletedMethod;
}
public bool IsCompleted => _isCompletedMethod(_customAwaiter);
public object GetResult() => _getResultMethod(_customAwaiter);
public void OnCompleted(Action continuation)
{
_onCompletedMethod(_customAwaiter, continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
// If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted.
// If not, fall back on using its OnCompleted.
//
// Why this is safe:
// - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it
// needs the execution context to be preserved (which it signals by calling OnCompleted), or
// that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not*
// to preserve and restore the context, so we prefer that where possible.
// - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted,
// there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen
// if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to
// pass the call on to the underlying awaitable's OnCompleted method.
var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod;
underlyingMethodToUse(_customAwaiter, continuation);
}
}
}
}

View File

@ -0,0 +1,151 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying
/// an <see cref="Expression"/> for mapping instances of that type to a C# awaitable.
/// </summary>
/// <remarks>
/// The main design goal here is to avoid taking a compile-time dependency on
/// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references
/// to FSharp types have to be constructed dynamically at runtime.
/// </remarks>
internal static class ObjectMethodExecutorFSharpSupport
{
private static object _fsharpValuesCacheLock = new object();
private static Assembly _fsharpCoreAssembly;
private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod;
private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty;
private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty;
public static bool TryBuildCoercerFromFSharpAsyncToAwaitable(
Type possibleFSharpAsyncType,
out Expression coerceToAwaitableExpression,
out Type awaitableType)
{
var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType
? possibleFSharpAsyncType.GetGenericTypeDefinition()
: null;
if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType))
{
coerceToAwaitableExpression = null;
awaitableType = null;
return false;
}
var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single();
awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType);
// coerceToAwaitableExpression = (object fsharpAsync) =>
// {
// return (object)FSharpAsync.StartAsTask<TResult>(
// (Microsoft.FSharp.Control.FSharpAsync<TResult>)fsharpAsync,
// FSharpOption<TaskCreationOptions>.None,
// FSharpOption<CancellationToken>.None);
// };
var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod
.MakeGenericMethod(awaiterResultType);
var coerceToAwaitableParam = Expression.Parameter(typeof(object));
coerceToAwaitableExpression = Expression.Lambda(
Expression.Convert(
Expression.Call(
startAsTaskClosedMethod,
Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType),
Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty),
Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)),
typeof(object)),
coerceToAwaitableParam);
return true;
}
private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType)
{
var typeFullName = possibleFSharpAsyncGenericType?.FullName;
if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal))
{
return false;
}
lock (_fsharpValuesCacheLock)
{
if (_fsharpCoreAssembly != null)
{
// Since we've already found the real FSharpAsync.Core assembly, we just have
// to check that the supplied FSharpAsync`1 type is the one from that assembly.
return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly;
}
else
{
// We'll keep trying to find the FSharp types/values each time any type called
// FSharpAsync`1 is supplied.
return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType);
}
}
}
private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType)
{
var assembly = possibleFSharpAsyncGenericType.Assembly;
var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1");
var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync");
if (fsharpOptionType == null || fsharpAsyncType == null)
{
return false;
}
// Get a reference to FSharpOption<TaskCreationOptions>.None
var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
.MakeGenericType(typeof(TaskCreationOptions));
_fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType
.GetTypeInfo()
.GetRuntimeProperty("None");
// Get a reference to FSharpOption<CancellationToken>.None
var fsharpOptionOfCancellationTokenType = fsharpOptionType
.MakeGenericType(typeof(CancellationToken));
_fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType
.GetTypeInfo()
.GetRuntimeProperty("None");
// Get a reference to FSharpAsync.StartAsTask<>
var fsharpAsyncMethods = fsharpAsyncType
.GetRuntimeMethods()
.Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal));
foreach (var candidateMethodInfo in fsharpAsyncMethods)
{
var parameters = candidateMethodInfo.GetParameters();
if (parameters.Length == 3
&& TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType)
&& parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType
&& parameters[2].ParameterType == fsharpOptionOfCancellationTokenType)
{
// This really does look like the correct method (and hence assembly).
_fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo;
_fsharpCoreAssembly = assembly;
break;
}
}
return _fsharpCoreAssembly != null;
}
private static bool TypesHaveSameIdentity(Type type1, Type type2)
{
return type1.Assembly == type2.Assembly
&& string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal)
&& string.Equals(type1.Name, type2.Name, StringComparison.Ordinal);
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Extensions.Internal
{
internal static class ProcessExtensions
{
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, TimeSpan timeout)
{
string stdout;
if (_isWindows)
{
RunProcessAndWaitForExit(
"taskkill",
$"/T /F /PID {process.Id}",
timeout,
out stdout);
}
else
{
var children = new HashSet<int>();
GetAllChildIdsUnix(process.Id, children, timeout);
foreach (var childId in children)
{
KillProcessUnix(childId, timeout);
}
KillProcessUnix(process.Id, timeout);
}
}
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{
string stdout;
var exitCode = RunProcessAndWaitForExit(
"pgrep",
$"-P {parentId}",
timeout,
out stdout);
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
{
using (var reader = new StringReader(stdout))
{
while (true)
{
var text = reader.ReadLine();
if (text == null)
{
return;
}
int id;
if (int.TryParse(text, out id))
{
children.Add(id);
// Recursively get the children
GetAllChildIdsUnix(id, children, timeout);
}
}
}
}
}
private static void KillProcessUnix(int processId, TimeSpan timeout)
{
string stdout;
RunProcessAndWaitForExit(
"kill",
$"-TERM {processId}",
timeout,
out stdout);
}
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
{
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(startInfo);
stdout = null;
if (process.WaitForExit((int)timeout.TotalMilliseconds))
{
stdout = process.StandardOutput.ReadToEnd();
}
else
{
process.Kill();
}
return process.ExitCode;
}
}
}

View File

@ -0,0 +1,110 @@
// 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;
using System.Reflection;
namespace Microsoft.Extensions.Internal
{
internal class PropertyActivator<TContext>
{
private readonly Func<TContext, object> _valueAccessor;
private readonly Action<object, object> _fastPropertySetter;
public PropertyActivator(
PropertyInfo propertyInfo,
Func<TContext, object> valueAccessor)
{
if (propertyInfo == null)
{
throw new ArgumentNullException(nameof(propertyInfo));
}
if (valueAccessor == null)
{
throw new ArgumentNullException(nameof(valueAccessor));
}
PropertyInfo = propertyInfo;
_valueAccessor = valueAccessor;
_fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo);
}
public PropertyInfo PropertyInfo { get; private set; }
public object Activate(object instance, TContext context)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
var value = _valueAccessor(context);
_fastPropertySetter(instance, value);
return value;
}
public static PropertyActivator<TContext>[] GetPropertiesToActivate(
Type type,
Type activateAttributeType,
Func<PropertyInfo, PropertyActivator<TContext>> createActivateInfo)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (activateAttributeType == null)
{
throw new ArgumentNullException(nameof(activateAttributeType));
}
if (createActivateInfo == null)
{
throw new ArgumentNullException(nameof(createActivateInfo));
}
return GetPropertiesToActivate(type, activateAttributeType, createActivateInfo, includeNonPublic: false);
}
public static PropertyActivator<TContext>[] GetPropertiesToActivate(
Type type,
Type activateAttributeType,
Func<PropertyInfo, PropertyActivator<TContext>> createActivateInfo,
bool includeNonPublic)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (activateAttributeType == null)
{
throw new ArgumentNullException(nameof(activateAttributeType));
}
if (createActivateInfo == null)
{
throw new ArgumentNullException(nameof(createActivateInfo));
}
var properties = type.GetRuntimeProperties()
.Where((property) =>
{
return
property.IsDefined(activateAttributeType) &&
property.GetIndexParameters().Length == 0 &&
property.SetMethod != null &&
!property.SetMethod.IsStatic;
});
if (!includeNonPublic)
{
properties = properties.Where(property => property.SetMethod.IsPublic);
}
return properties.Select(createActivateInfo).ToArray();
}
}
}

View File

@ -0,0 +1,526 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Microsoft.Extensions.Internal
{
internal class PropertyHelper
{
// Delegate type for a by-ref property getter
private delegate TValue ByRefFunc<TDeclaringType, TValue>(ref TDeclaringType arg);
private static readonly MethodInfo CallPropertyGetterOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter));
private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference));
private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter));
private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference));
private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter));
// Using an array rather than IEnumerable, as target will be called on the hot path numerous times.
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> PropertiesCache =
new ConcurrentDictionary<Type, PropertyHelper[]>();
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> VisiblePropertiesCache =
new ConcurrentDictionary<Type, PropertyHelper[]>();
private Action<object, object> _valueSetter;
private Func<object, object> _valueGetter;
/// <summary>
/// Initializes a fast <see cref="PropertyHelper"/>.
/// This constructor does not cache the helper. For caching, use <see cref="GetProperties(Type)"/>.
/// </summary>
public PropertyHelper(PropertyInfo property)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
Property = property;
Name = property.Name;
}
/// <summary>
/// Gets the backing <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo Property { get; }
/// <summary>
/// Gets (or sets in derived types) the property name.
/// </summary>
public virtual string Name { get; protected set; }
/// <summary>
/// Gets the property value getter.
/// </summary>
public Func<object, object> ValueGetter
{
get
{
if (_valueGetter == null)
{
_valueGetter = MakeFastPropertyGetter(Property);
}
return _valueGetter;
}
}
/// <summary>
/// Gets the property value setter.
/// </summary>
public Action<object, object> ValueSetter
{
get
{
if (_valueSetter == null)
{
_valueSetter = MakeFastPropertySetter(Property);
}
return _valueSetter;
}
}
/// <summary>
/// Returns the property value for the specified <paramref name="instance"/>.
/// </summary>
/// <param name="instance">The object whose property value will be returned.</param>
/// <returns>The property value.</returns>
public object GetValue(object instance)
{
return ValueGetter(instance);
}
/// <summary>
/// Sets the property value for the specified <paramref name="instance" />.
/// </summary>
/// <param name="instance">The object whose property value will be set.</param>
/// <param name="value">The property value.</param>
public void SetValue(object instance, object value)
{
ValueSetter(instance, value);
}
/// <summary>
/// Creates and caches fast property helpers that expose getters for every public get property on the
/// underlying type.
/// </summary>
/// <param name="typeInfo">The type info to extract property accessors for.</param>
/// <returns>A cached array of all public properties of the specified type.
/// </returns>
public static PropertyHelper[] GetProperties(TypeInfo typeInfo)
{
return GetProperties(typeInfo.AsType());
}
/// <summary>
/// Creates and caches fast property helpers that expose getters for every public get property on the
/// specified type.
/// </summary>
/// <param name="type">The type to extract property accessors for.</param>
/// <returns>A cached array of all public properties of the specified type.
/// </returns>
public static PropertyHelper[] GetProperties(Type type)
{
return GetProperties(type, CreateInstance, PropertiesCache);
}
/// <summary>
/// <para>
/// Creates and caches fast property helpers that expose getters for every non-hidden get property
/// on the specified type.
/// </para>
/// <para>
/// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been
/// hidden by definitions using the <c>new</c> keyword.
/// </para>
/// </summary>
/// <param name="typeInfo">The type info to extract property accessors for.</param>
/// <returns>
/// A cached array of all public properties of the specified type.
/// </returns>
public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo)
{
return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache);
}
/// <summary>
/// <para>
/// Creates and caches fast property helpers that expose getters for every non-hidden get property
/// on the specified type.
/// </para>
/// <para>
/// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been
/// hidden by definitions using the <c>new</c> keyword.
/// </para>
/// </summary>
/// <param name="type">The type to extract property accessors for.</param>
/// <returns>
/// A cached array of all public properties of the specified type.
/// </returns>
public static PropertyHelper[] GetVisibleProperties(Type type)
{
return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache);
}
/// <summary>
/// Creates a single fast property getter. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
/// <returns>a fast getter.</returns>
/// <remarks>
/// This method is more memory efficient than a dynamically compiled lambda, and about the
/// same speed.
/// </remarks>
public static Func<object, object> MakeFastPropertyGetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
return MakeFastPropertyGetter(
propertyInfo,
CallPropertyGetterOpenGenericMethod,
CallPropertyGetterByReferenceOpenGenericMethod);
}
/// <summary>
/// Creates a single fast property getter which is safe for a null input object. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
/// <returns>a fast getter.</returns>
/// <remarks>
/// This method is more memory efficient than a dynamically compiled lambda, and about the
/// same speed.
/// </remarks>
public static Func<object, object> MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
return MakeFastPropertyGetter(
propertyInfo,
CallNullSafePropertyGetterOpenGenericMethod,
CallNullSafePropertyGetterByReferenceOpenGenericMethod);
}
private static Func<object, object> MakeFastPropertyGetter(
PropertyInfo propertyInfo,
MethodInfo propertyGetterWrapperMethod,
MethodInfo propertyGetterByRefWrapperMethod)
{
Debug.Assert(propertyInfo != null);
// Must be a generic method with a Func<,> parameter
Debug.Assert(propertyGetterWrapperMethod != null);
Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition);
Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2);
// Must be a generic method with a ByRefFunc<,> parameter
Debug.Assert(propertyGetterByRefWrapperMethod != null);
Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition);
Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2);
var getMethod = propertyInfo.GetMethod;
Debug.Assert(getMethod != null);
Debug.Assert(!getMethod.IsStatic);
Debug.Assert(getMethod.GetParameters().Length == 0);
// Instance methods in the CLR can be turned into static methods where the first parameter
// is open over "target". This parameter is always passed by reference, so we have a code
// path for value types and a code path for reference types.
if (getMethod.DeclaringType.GetTypeInfo().IsValueType)
{
// Create a delegate (ref TDeclaringType) -> TValue
return MakeFastPropertyGetter(
typeof(ByRefFunc<,>),
getMethod,
propertyGetterByRefWrapperMethod);
}
else
{
// Create a delegate TDeclaringType -> TValue
return MakeFastPropertyGetter(
typeof(Func<,>),
getMethod,
propertyGetterWrapperMethod);
}
}
private static Func<object, object> MakeFastPropertyGetter(
Type openGenericDelegateType,
MethodInfo propertyGetMethod,
MethodInfo openGenericWrapperMethod)
{
var typeInput = propertyGetMethod.DeclaringType;
var typeOutput = propertyGetMethod.ReturnType;
var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput);
var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType);
var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput);
var accessorDelegate = wrapperDelegateMethod.CreateDelegate(
typeof(Func<object, object>),
propertyGetterDelegate);
return (Func<object, object>)accessorDelegate;
}
/// <summary>
/// Creates a single fast property setter for reference types. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the setter for.</param>
/// <returns>a fast getter.</returns>
/// <remarks>
/// This method is more memory efficient than a dynamically compiled lambda, and about the
/// same speed. This only works for reference types.
/// </remarks>
public static Action<object, object> MakeFastPropertySetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType);
var setMethod = propertyInfo.SetMethod;
Debug.Assert(setMethod != null);
Debug.Assert(!setMethod.IsStatic);
Debug.Assert(setMethod.ReturnType == typeof(void));
var parameters = setMethod.GetParameters();
Debug.Assert(parameters.Length == 1);
// Instance methods in the CLR can be turned into static methods where the first parameter
// is open over "target". This parameter is always passed by reference, so we have a code
// path for value types and a code path for reference types.
var typeInput = setMethod.DeclaringType;
var parameterType = parameters[0].ParameterType;
// Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; }
var propertySetterAsAction =
setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType));
var callPropertySetterClosedGenericMethod =
CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType);
var callPropertySetterDelegate =
callPropertySetterClosedGenericMethod.CreateDelegate(
typeof(Action<object, object>), propertySetterAsAction);
return (Action<object, object>)callPropertySetterDelegate;
}
/// <summary>
/// Given an object, adds each instance property with a public get method as a key and its
/// associated value to a dictionary.
///
/// If the object is already an <see cref="IDictionary{String, Object}"/> instance, then a copy
/// is returned.
/// </summary>
/// <remarks>
/// The implementation of PropertyHelper will cache the property accessors per-type. This is
/// faster when the same type is used multiple times with ObjectToDictionary.
/// </remarks>
public static IDictionary<string, object> ObjectToDictionary(object value)
{
var dictionary = value as IDictionary<string, object>;
if (dictionary != null)
{
return new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
}
dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
if (value != null)
{
foreach (var helper in GetProperties(value.GetType()))
{
dictionary[helper.Name] = helper.GetValue(value);
}
}
return dictionary;
}
private static PropertyHelper CreateInstance(PropertyInfo property)
{
return new PropertyHelper(property);
}
// Called via reflection
private static object CallPropertyGetter<TDeclaringType, TValue>(
Func<TDeclaringType, TValue> getter,
object target)
{
return getter((TDeclaringType)target);
}
// Called via reflection
private static object CallPropertyGetterByReference<TDeclaringType, TValue>(
ByRefFunc<TDeclaringType, TValue> getter,
object target)
{
var unboxed = (TDeclaringType)target;
return getter(ref unboxed);
}
// Called via reflection
private static object CallNullSafePropertyGetter<TDeclaringType, TValue>(
Func<TDeclaringType, TValue> getter,
object target)
{
if (target == null)
{
return null;
}
return getter((TDeclaringType)target);
}
// Called via reflection
private static object CallNullSafePropertyGetterByReference<TDeclaringType, TValue>(
ByRefFunc<TDeclaringType, TValue> getter,
object target)
{
if (target == null)
{
return null;
}
var unboxed = (TDeclaringType)target;
return getter(ref unboxed);
}
private static void CallPropertySetter<TDeclaringType, TValue>(
Action<TDeclaringType, TValue> setter,
object target,
object value)
{
setter((TDeclaringType)target, (TValue)value);
}
protected static PropertyHelper[] GetVisibleProperties(
Type type,
Func<PropertyInfo, PropertyHelper> createPropertyHelper,
ConcurrentDictionary<Type, PropertyHelper[]> allPropertiesCache,
ConcurrentDictionary<Type, PropertyHelper[]> visiblePropertiesCache)
{
PropertyHelper[] result;
if (visiblePropertiesCache.TryGetValue(type, out result))
{
return result;
}
// The simple and common case, this is normal POCO object - no need to allocate.
var allPropertiesDefinedOnType = true;
var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache);
foreach (var propertyHelper in allProperties)
{
if (propertyHelper.Property.DeclaringType != type)
{
allPropertiesDefinedOnType = false;
break;
}
}
if (allPropertiesDefinedOnType)
{
result = allProperties;
visiblePropertiesCache.TryAdd(type, result);
return result;
}
// There's some inherited properties here, so we need to check for hiding via 'new'.
var filteredProperties = new List<PropertyHelper>(allProperties.Length);
foreach (var propertyHelper in allProperties)
{
var declaringType = propertyHelper.Property.DeclaringType;
if (declaringType == type)
{
filteredProperties.Add(propertyHelper);
continue;
}
// If this property was declared on a base type then look for the definition closest to the
// the type to see if we should include it.
var ignoreProperty = false;
// Walk up the hierarchy until we find the type that actually declares this
// PropertyInfo.
var currentTypeInfo = type.GetTypeInfo();
var declaringTypeInfo = declaringType.GetTypeInfo();
while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo)
{
// We've found a 'more proximal' public definition
var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name);
if (declaredProperty != null)
{
ignoreProperty = true;
break;
}
currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo();
}
if (!ignoreProperty)
{
filteredProperties.Add(propertyHelper);
}
}
result = filteredProperties.ToArray();
visiblePropertiesCache.TryAdd(type, result);
return result;
}
protected static PropertyHelper[] GetProperties(
Type type,
Func<PropertyInfo, PropertyHelper> createPropertyHelper,
ConcurrentDictionary<Type, PropertyHelper[]> cache)
{
// Unwrap nullable types. This means Nullable<T>.Value and Nullable<T>.HasValue will not be
// part of the sequence of properties returned by this method.
type = Nullable.GetUnderlyingType(type) ?? type;
PropertyHelper[] helpers;
if (!cache.TryGetValue(type, out helpers))
{
// We avoid loading indexed properties using the Where statement.
var properties = type.GetRuntimeProperties().Where(IsInterestingProperty);
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsInterface)
{
// Reflection does not return information about inherited properties on the interface itself.
properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany(
interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty)));
}
helpers = properties.Select(p => createPropertyHelper(p)).ToArray();
cache.TryAdd(type, helpers);
}
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 &&
property.GetMethod.IsPublic &&
!property.GetMethod.IsStatic &&
property.GetMethod.GetParameters().Length == 0;
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.RazorViews
{
internal class AttributeValue
{
public AttributeValue(string prefix, object value, bool literal)
{
Prefix = prefix;
Value = value;
Literal = literal;
}
public string Prefix { get; }
public object Value { get; }
public bool Literal { get; }
public static AttributeValue FromTuple(Tuple<string, object, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static AttributeValue FromTuple(Tuple<string, string, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static implicit operator AttributeValue(Tuple<string, object, bool> value)
{
return FromTuple(value);
}
}
}

View File

@ -0,0 +1,279 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.Extensions.RazorViews
{
/// <summary>
/// Infrastructure
/// </summary>
internal abstract class BaseView
{
private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private readonly Stack<TextWriter> _textWriterStack = new Stack<TextWriter>();
/// <summary>
/// The request context
/// </summary>
protected HttpContext Context { get; private set; }
/// <summary>
/// The request
/// </summary>
protected HttpRequest Request { get; private set; }
/// <summary>
/// The response
/// </summary>
protected HttpResponse Response { get; private set; }
/// <summary>
/// The output stream
/// </summary>
protected TextWriter Output { get; private set; }
/// <summary>
/// Html encoder used to encode content.
/// </summary>
protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default;
/// <summary>
/// Url encoder used to encode content.
/// </summary>
protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default;
/// <summary>
/// JavaScript encoder used to encode content.
/// </summary>
protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default;
/// <summary>
/// Execute an individual request
/// </summary>
/// <param name="context"></param>
public async Task ExecuteAsync(HttpContext context)
{
Context = context;
Request = Context.Request;
Response = Context.Response;
Output = new StreamWriter(Response.Body, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync();
Output.Dispose();
}
/// <summary>
/// Execute an individual request
/// </summary>
public abstract Task ExecuteAsync();
protected virtual void PushWriter(TextWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
_textWriterStack.Push(Output);
Output = writer;
}
protected virtual TextWriter PopWriter()
{
Output = _textWriterStack.Pop();
return Output;
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
protected void WriteLiteral(object value)
{
WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture));
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void WriteLiteral(string value)
{
if (!string.IsNullOrEmpty(value))
{
Output.Write(value);
}
}
private List<string> AttributeValues { get; set; }
protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno)
{
if (AttributeValues == null)
{
AttributeValues = new List<string>();
}
AttributeValues.Add(value.ToString());
}
private string AttributeEnding { get; set; }
protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy)
{
Debug.Assert(string.IsNullOrEmpty(AttributeEnding));
Output.Write(begining);
AttributeEnding = ending;
}
protected void EndWriteAttribute()
{
Debug.Assert(!string.IsNullOrEmpty(AttributeEnding));
var attributes = string.Join(" ", AttributeValues);
Output.Write(attributes);
AttributeValues = null;
Output.Write(AttributeEnding);
AttributeEnding = null;
}
/// <summary>
/// Writes the given attribute to the given writer
/// </summary>
/// <param name="name">The name of the attribute to write</param>
/// <param name="leader">The value of the prefix</param>
/// <param name="trailer">The value of the suffix</param>
/// <param name="values">The <see cref="AttributeValue"/>s to write.</param>
protected void WriteAttribute(
string name,
string leader,
string trailer,
params AttributeValue[] values)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (leader == null)
{
throw new ArgumentNullException(nameof(leader));
}
if (trailer == null)
{
throw new ArgumentNullException(nameof(trailer));
}
WriteLiteral(leader);
foreach (var value in values)
{
WriteLiteral(value.Prefix);
// The special cases here are that the value we're writing might already be a string, or that the
// value might be a bool. If the value is the bool 'true' we want to write the attribute name
// instead of the string 'true'. If the value is the bool 'false' we don't want to write anything.
// Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself.
string stringValue;
if (value.Value is bool)
{
if ((bool)value.Value)
{
stringValue = name;
}
else
{
continue;
}
}
else
{
stringValue = value.Value as string;
}
// Call the WriteTo(string) overload when possible
if (value.Literal && stringValue != null)
{
WriteLiteral(stringValue);
}
else if (value.Literal)
{
WriteLiteral(value.Value);
}
else if (stringValue != null)
{
Write(stringValue);
}
else
{
Write(value.Value);
}
}
WriteLiteral(trailer);
}
/// <summary>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked
/// </summary>
/// <param name="result">The <see cref="HelperResult"/> to invoke</param>
protected void Write(HelperResult result)
{
Write(result);
}
/// <summary>
/// Writes the specified <paramref name="value"/> to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to
/// <see cref="Output"/>.
/// </remarks>
protected void Write(object value)
{
if (value is HelperResult helperResult)
{
helperResult.WriteTo(Output);
}
else
{
Write(Convert.ToString(value, CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void Write(string value)
{
WriteLiteral(HtmlEncoder.Encode(value));
}
protected string HtmlEncodeAndReplaceLineBreaks(string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
// Split on line breaks before passing it through the encoder.
return string.Join("<br />" + Environment.NewLine,
input.Split(new[] { "\r\n" }, StringSplitOptions.None)
.SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None))
.Select(HtmlEncoder.Encode));
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
namespace Microsoft.Extensions.RazorViews
{
/// <summary>
/// Represents a deferred write operation in a <see cref="BaseView"/>.
/// </summary>
internal class HelperResult
{
/// <summary>
/// Creates a new instance of <see cref="HelperResult"/>.
/// </summary>
/// <param name="action">The delegate to invoke when <see cref="WriteTo(TextWriter)"/> is called.</param>
public HelperResult(Action<TextWriter> action)
{
WriteAction = action;
}
public Action<TextWriter> WriteAction { get; }
/// <summary>
/// Method invoked to produce content from the <see cref="HelperResult"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
public void WriteTo(TextWriter writer)
{
WriteAction(writer);
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Security.Claims;
namespace Microsoft.Extensions.Internal
{
/// <summary>
/// Helper code used when implementing authentication middleware
/// </summary>
internal static class SecurityHelper
{
/// <summary>
/// Add all ClaimsIdentities from an additional ClaimPrincipal to the ClaimsPrincipal
/// Merges a new claims principal, placing all new identities first, and eliminating
/// any empty unauthenticated identities from context.User
/// </summary>
/// <param name="existingPrincipal">The <see cref="ClaimsPrincipal"/> containing existing <see cref="ClaimsIdentity"/>.</param>
/// <param name="additionalPrincipal">The <see cref="ClaimsPrincipal"/> containing <see cref="ClaimsIdentity"/> to be added.</param>
public static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal existingPrincipal, ClaimsPrincipal additionalPrincipal)
{
var newPrincipal = new ClaimsPrincipal();
// New principal identities go first
if (additionalPrincipal != null)
{
newPrincipal.AddIdentities(additionalPrincipal.Identities);
}
// Then add any existing non empty or authenticated identities
if (existingPrincipal != null)
{
newPrincipal.AddIdentities(existingPrincipal.Identities.Where(i => i.IsAuthenticated || i.Claims.Any()));
}
return newPrincipal;
}
}
}

View File

@ -0,0 +1,29 @@
// 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;
namespace Microsoft.Extensions.StackTrace.Sources
{
/// <summary>
/// Contains details for individual exception messages.
/// </summary>
internal class ExceptionDetails
{
/// <summary>
/// An individual exception
/// </summary>
public Exception Error { get; set; }
/// <summary>
/// The generated stack frames
/// </summary>
public IEnumerable<StackFrameSourceCodeInfo> StackFrames { get; set; }
/// <summary>
/// Gets or sets the summary message.
/// </summary>
public string ErrorMessage { get; set; }
}
}

View File

@ -0,0 +1,170 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class ExceptionDetailsProvider
{
private readonly IFileProvider _fileProvider;
private readonly int _sourceCodeLineCount;
public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount)
{
_fileProvider = fileProvider;
_sourceCodeLineCount = sourceCodeLineCount;
}
public IEnumerable<ExceptionDetails> GetDetails(Exception exception)
{
var exceptions = FlattenAndReverseExceptionTree(exception);
foreach (var ex in exceptions)
{
yield return new ExceptionDetails
{
Error = ex,
StackFrames = StackTraceHelper.GetFrames(ex)
.Select(frame => GetStackFrameSourceCodeInfo(
frame.MethodDisplayInfo.ToString(),
frame.FilePath,
frame.LineNumber))
};
}
}
private static IEnumerable<Exception> FlattenAndReverseExceptionTree(Exception ex)
{
// ReflectionTypeLoadException is special because the details are in
// the LoaderExceptions property
var typeLoadException = ex as ReflectionTypeLoadException;
if (typeLoadException != null)
{
var typeLoadExceptions = new List<Exception>();
foreach (var loadException in typeLoadException.LoaderExceptions)
{
typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException));
}
typeLoadExceptions.Add(ex);
return typeLoadExceptions;
}
var list = new List<Exception>();
if (ex is AggregateException aggregateException)
{
list.Add(ex);
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
{
list.Add(innerException);
}
}
else
{
while (ex != null)
{
list.Add(ex);
ex = ex.InnerException;
}
list.Reverse();
}
return list;
}
// make it internal to enable unit testing
internal StackFrameSourceCodeInfo GetStackFrameSourceCodeInfo(string method, string filePath, int lineNumber)
{
var stackFrame = new StackFrameSourceCodeInfo
{
Function = method,
File = filePath,
Line = lineNumber
};
if (string.IsNullOrEmpty(stackFrame.File))
{
return stackFrame;
}
IEnumerable<string> lines = null;
if (File.Exists(stackFrame.File))
{
lines = File.ReadLines(stackFrame.File);
}
else
{
// Handle relative paths and embedded files
var fileInfo = _fileProvider.GetFileInfo(stackFrame.File);
if (fileInfo.Exists)
{
// ReadLines doesn't accept a stream. Use ReadLines as its more efficient
// relative to reading lines via stream reader
if (!string.IsNullOrEmpty(fileInfo.PhysicalPath))
{
lines = File.ReadLines(fileInfo.PhysicalPath);
}
else
{
lines = ReadLines(fileInfo);
}
}
}
if (lines != null)
{
ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line);
}
return stackFrame;
}
// make it internal to enable unit testing
internal void ReadFrameContent(
StackFrameSourceCodeInfo frame,
IEnumerable<string> allLines,
int errorStartLineNumberInFile,
int errorEndLineNumberInFile)
{
// Get the line boundaries in the file to be read and read all these lines at once into an array.
var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _sourceCodeLineCount, 1);
var postErrorLineNumberInFile = errorEndLineNumberInFile + _sourceCodeLineCount;
var codeBlock = allLines
.Skip(preErrorLineNumberInFile - 1)
.Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1)
.ToArray();
var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1;
var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile;
frame.PreContextLine = preErrorLineNumberInFile;
frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray();
frame.ContextCode = codeBlock
.Skip(errorStartLineNumberInArray)
.Take(numOfErrorLines)
.ToArray();
frame.PostContextCode = codeBlock
.Skip(errorStartLineNumberInArray + numOfErrorLines)
.ToArray();
}
private static IEnumerable<string> ReadLines(IFileInfo fileInfo)
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class MethodDisplayInfo
{
public string DeclaringTypeName { get; set; }
public string Name { get; set; }
public string GenericArguments { get; set; }
public string SubMethod { get; set; }
public IEnumerable<ParameterDisplayInfo> Parameters { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
if (!string.IsNullOrEmpty(DeclaringTypeName))
{
builder
.Append(DeclaringTypeName)
.Append(".");
}
builder.Append(Name);
builder.Append(GenericArguments);
builder.Append("(");
builder.Append(string.Join(", ", Parameters.Select(p => p.ToString())));
builder.Append(")");
if (!string.IsNullOrEmpty(SubMethod))
{
builder.Append("+");
builder.Append(SubMethod);
builder.Append("()");
}
return builder.ToString();
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class ParameterDisplayInfo
{
public string Name { get; set; }
public string Type { get; set; }
public string Prefix { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
if (!string.IsNullOrEmpty(Prefix))
{
builder
.Append(Prefix)
.Append(" ");
}
builder.Append(Type);
builder.Append(" ");
builder.Append(Name);
return builder.ToString();
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class PortablePdbReader : IDisposable
{
private readonly Dictionary<string, MetadataReaderProvider> _cache =
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset)
{
if (method.Module.Assembly.IsDynamic)
{
return;
}
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
if (metadataReader == null)
{
return;
}
var methodToken = MetadataTokens.Handle(method.MetadataToken);
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
if (!handle.IsNil)
{
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
var sequencePoints = methodDebugInfo.GetSequencePoints();
SequencePoint? bestPointSoFar = null;
foreach (var point in sequencePoints)
{
if (point.Offset > IlOffset)
{
break;
}
if (point.StartLine != SequencePoint.HiddenLine)
{
bestPointSoFar = point;
}
}
if (bestPointSoFar.HasValue)
{
frameInfo.LineNumber = bestPointSoFar.Value.StartLine;
frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
}
}
}
private MetadataReader GetMetadataReader(string assemblyPath)
{
MetadataReaderProvider provider = null;
if (!_cache.TryGetValue(assemblyPath, out provider))
{
var pdbPath = GetPdbPath(assemblyPath);
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath))
{
var pdbStream = File.OpenRead(pdbPath);
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
}
_cache[assemblyPath] = provider;
}
return provider?.GetMetadataReader();
}
private static string GetPdbPath(string assemblyPath)
{
if (string.IsNullOrEmpty(assemblyPath))
{
return null;
}
if (File.Exists(assemblyPath))
{
var peStream = File.OpenRead(assemblyPath);
using (var peReader = new PEReader(peStream))
{
foreach (var entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
var peDirectory = Path.GetDirectoryName(assemblyPath);
return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
}
}
}
}
return null;
}
private static bool IsPortable(string pdbPath)
{
using (var pdbStream = File.OpenRead(pdbPath))
{
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
}
}
public void Dispose()
{
foreach (var entry in _cache)
{
entry.Value?.Dispose();
}
_cache.Clear();
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class StackFrameInfo
{
public int LineNumber { get; set; }
public string FilePath { get; set; }
public StackFrame StackFrame { get; set; }
public MethodDisplayInfo MethodDisplayInfo { get; set; }
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.StackTrace.Sources
{
/// <summary>
/// Contains the source code where the exception occurred.
/// </summary>
internal class StackFrameSourceCodeInfo
{
/// <summary>
/// Function containing instruction
/// </summary>
public string Function { get; set; }
/// <summary>
/// File containing the instruction
/// </summary>
public string File { get; set; }
/// <summary>
/// The line number of the instruction
/// </summary>
public int Line { get; set; }
/// <summary>
/// The line preceding the frame line
/// </summary>
public int PreContextLine { get; set; }
/// <summary>
/// Lines of code before the actual error line(s).
/// </summary>
public IEnumerable<string> PreContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Line(s) of code responsible for the error.
/// </summary>
public IEnumerable<string> ContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Lines of code after the actual error line(s).
/// </summary>
public IEnumerable<string> PostContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Specific error details for this stack frame.
/// </summary>
public string ErrorDetails { get; set; }
}
}

View File

@ -0,0 +1,261 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using Microsoft.Extensions.Internal;
namespace Microsoft.Extensions.StackTrace.Sources
{
internal class StackTraceHelper
{
public static IList<StackFrameInfo> GetFrames(Exception exception)
{
var frames = new List<StackFrameInfo>();
if (exception == null)
{
return frames;
}
using (var portablePdbReader = new PortablePdbReader())
{
var needFileInfo = true;
var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo);
var stackFrames = stackTrace.GetFrames();
if (stackFrames == null)
{
return frames;
}
for (var i = 0; i < stackFrames.Length; i++)
{
var frame = stackFrames[i];
var method = frame.GetMethod();
// Always show last stackFrame
if (!ShowInStackTrace(method) && i < stackFrames.Length - 1)
{
continue;
}
var stackFrame = new StackFrameInfo
{
StackFrame = frame,
FilePath = frame.GetFileName(),
LineNumber = frame.GetFileLineNumber(),
MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()),
};
if (string.IsNullOrEmpty(stackFrame.FilePath))
{
// .NET Framework and older versions of mono don't support portable PDBs
// so we read it manually to get file name and line information
portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset());
}
frames.Add(stackFrame);
}
return frames;
}
}
internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method)
{
// Special case: no method available
if (method == null)
{
return null;
}
var methodDisplayInfo = new MethodDisplayInfo();
// Type name
var type = method.DeclaringType;
var methodName = method.Name;
if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) &&
(typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type)))
{
// Convert StateMachine methods to correct overload +MoveNext()
if (TryResolveStateMachineMethod(ref method, out type))
{
methodDisplayInfo.SubMethod = methodName;
}
}
// ResolveStateMachineMethod may have set declaringType to null
if (type != null)
{
methodDisplayInfo.DeclaringTypeName = TypeNameHelper.GetTypeDisplayName(type, includeGenericParameterNames: true);
}
// Method name
methodDisplayInfo.Name = method.Name;
if (method.IsGenericMethod)
{
var genericArguments = string.Join(", ", method.GetGenericArguments()
.Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
methodDisplayInfo.GenericArguments += "<" + genericArguments + ">";
}
// Method parameters
methodDisplayInfo.Parameters = method.GetParameters().Select(parameter =>
{
var parameterType = parameter.ParameterType;
var prefix = string.Empty;
if (parameter.IsOut)
{
prefix = "out";
}
else if (parameterType != null && parameterType.IsByRef)
{
prefix = "ref";
}
var parameterTypeString = "?";
if (parameterType != null)
{
if (parameterType.IsByRef)
{
parameterType = parameterType.GetElementType();
}
parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true);
}
return new ParameterDisplayInfo
{
Prefix = prefix,
Name = parameter.Name,
Type = parameterTypeString,
};
});
return methodDisplayInfo;
}
private static bool ShowInStackTrace(MethodBase method)
{
Debug.Assert(method != null);
// Don't show any methods marked with the StackTraceHiddenAttribute
// https://github.com/dotnet/coreclr/pull/14652
if (HasStackTraceHiddenAttribute(method))
{
return false;
}
var type = method.DeclaringType;
if (type == null)
{
return true;
}
if (HasStackTraceHiddenAttribute(type))
{
return false;
}
// Fallbacks for runtime pre-StackTraceHiddenAttribute
if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw")
{
return false;
}
else if (type == typeof(TaskAwaiter) ||
type == typeof(TaskAwaiter<>) ||
type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter))
{
switch (method.Name)
{
case "HandleNonSuccessAndDebuggerNotification":
case "ThrowForNonSuccess":
case "ValidateEnd":
case "GetResult":
return false;
}
}
return true;
}
private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
{
Debug.Assert(method != null);
Debug.Assert(method.DeclaringType != null);
declaringType = method.DeclaringType;
var parentType = declaringType.DeclaringType;
if (parentType == null)
{
return false;
}
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods == null)
{
return false;
}
foreach (var candidateMethod in methods)
{
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
if (attributes == null)
{
continue;
}
foreach (var asma in attributes)
{
if (asma.StateMachineType == declaringType)
{
method = candidateMethod;
declaringType = candidateMethod.DeclaringType;
// Mark the iterator as changed; so it gets the + annotation of the original method
// async statemachines resolve directly to their builder methods so aren't marked as changed
return asma is IteratorStateMachineAttribute;
}
}
}
return false;
}
private static bool HasStackTraceHiddenAttribute(MemberInfo memberInfo)
{
IList<CustomAttributeData> attributes;
try
{
// Accessing MembmerInfo.GetCustomAttributesData throws for some types (such as types in dynamically generated assemblies).
// We'll skip looking up StackTraceHiddenAttributes on such types.
attributes = memberInfo.GetCustomAttributesData();
}
catch
{
return false;
}
for (var i = 0; i < attributes.Count; i++)
{
if (attributes[i].AttributeType.Name == "StackTraceHiddenAttribute")
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,160 @@
// 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.Text;
using System.Collections.Generic;
namespace Microsoft.Extensions.Internal
{
internal class TypeNameHelper
{
private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string>
{
{ typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" }
};
public static string GetTypeDisplayName(object item, bool fullName = true)
{
return item == null ? null : GetTypeDisplayName(item.GetType(), fullName);
}
/// <summary>
/// Pretty print a type name.
/// </summary>
/// <param name="type">The <see cref="Type"/>.</param>
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
/// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
{
var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
return builder.ToString();
}
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
{
if (type.IsGenericType)
{
var genericArguments = type.GetGenericArguments();
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
}
else if (type.IsArray)
{
ProcessArrayType(builder, type, options);
}
else if (_builtInTypeNames.TryGetValue(type, out var builtInName))
{
builder.Append(builtInName);
}
else if (type.IsGenericParameter)
{
if (options.IncludeGenericParameterNames)
{
builder.Append(type.Name);
}
}
else
{
builder.Append(options.FullName ? type.FullName : type.Name);
}
}
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
{
var innerType = type;
while (innerType.IsArray)
{
innerType = innerType.GetElementType();
}
ProcessType(builder, innerType, options);
while (type.IsArray)
{
builder.Append('[');
builder.Append(',', type.GetArrayRank() - 1);
builder.Append(']');
type = type.GetElementType();
}
}
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options)
{
var offset = 0;
if (type.IsNested)
{
offset = type.DeclaringType.GetGenericArguments().Length;
}
if (options.FullName)
{
if (type.IsNested)
{
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+');
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
builder.Append(type.Namespace);
builder.Append('.');
}
}
var genericPartIndex = type.Name.IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);
return;
}
builder.Append(type.Name, 0, genericPartIndex);
builder.Append('<');
for (var i = offset; i < length; i++)
{
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length)
{
continue;
}
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{
builder.Append(' ');
}
}
builder.Append('>');
}
private struct DisplayNameOptions
{
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
{
FullName = fullName;
IncludeGenericParameterNames = includeGenericParameterNames;
}
public bool FullName { get; }
public bool IncludeGenericParameterNames { get; }
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
namespace Microsoft.Extensions.Internal
{
internal struct ValueStopwatch
{
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
private long _startTimestamp;
public bool IsActive => _startTimestamp != 0;
private ValueStopwatch(long startTimestamp)
{
_startTimestamp = startTimestamp;
}
public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
public TimeSpan GetElapsedTime()
{
// Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
// So it being 0 is a clear indication of default(ValueStopwatch)
if (!IsActive)
{
throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
}
var end = Stopwatch.GetTimestamp();
var timestampDelta = end - _startTimestamp;
var ticks = (long)(TimestampToTicks * timestampDelta);
return new TimeSpan(ticks);
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization;
namespace Microsoft.Extensions.WebEncoders.Sources
{
// TODO using a resx file. project.json, unfortunately, fails to embed resx files when there are also compile items
// in the contentFiles section. Revisit once we convert repos to MSBuild
internal static class EncoderResources
{
/// <summary>
/// Invalid {0}, {1} or {2} length.
/// </summary>
internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length.";
/// <summary>
/// Malformed input: {0} is an invalid input length.
/// </summary>
internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length.";
/// <summary>
/// Invalid {0}, {1} or {2} length.
/// </summary>
internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2);
}
/// <summary>
/// Malformed input: {0} is an invalid input length.
/// </summary>
internal static string FormatWebEncoders_MalformedInput(object p0)
{
return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0);
}
}
}

View File

@ -0,0 +1,388 @@
// 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.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.WebEncoders.Sources;
#if WebEncoders_In_WebUtilities
namespace Microsoft.AspNetCore.WebUtilities
#else
namespace Microsoft.Extensions.Internal
#endif
{
/// <summary>
/// Contains utility APIs to assist with common encoding and decoding operations.
/// </summary>
#if WebEncoders_In_WebUtilities
public
#else
internal
#endif
static class WebEncoders
{
private static readonly byte[] EmptyBytes = new byte[0];
/// <summary>
/// Decodes a base64url-encoded string.
/// </summary>
/// <param name="input">The base64url-encoded input to decode.</param>
/// <returns>The base64url-decoded form of the input.</returns>
/// <remarks>
/// The input must not contain any whitespace or padding characters.
/// Throws <see cref="FormatException"/> if the input is malformed.
/// </remarks>
public static byte[] Base64UrlDecode(string input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
return Base64UrlDecode(input, offset: 0, count: input.Length);
}
/// <summary>
/// Decodes a base64url-encoded substring of a given string.
/// </summary>
/// <param name="input">A string containing the base64url-encoded input to decode.</param>
/// <param name="offset">The position in <paramref name="input"/> at which decoding should begin.</param>
/// <param name="count">The number of characters in <paramref name="input"/> to decode.</param>
/// <returns>The base64url-decoded form of the input.</returns>
/// <remarks>
/// The input must not contain any whitespace or padding characters.
/// Throws <see cref="FormatException"/> if the input is malformed.
/// </remarks>
public static byte[] Base64UrlDecode(string input, int offset, int count)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
ValidateParameters(input.Length, nameof(input), offset, count);
// Special-case empty input
if (count == 0)
{
return EmptyBytes;
}
// Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form.
var buffer = new char[GetArraySizeRequiredToDecode(count)];
return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count);
}
/// <summary>
/// Decodes a base64url-encoded <paramref name="input"/> into a <c>byte[]</c>.
/// </summary>
/// <param name="input">A string containing the base64url-encoded input to decode.</param>
/// <param name="offset">The position in <paramref name="input"/> at which decoding should begin.</param>
/// <param name="buffer">
/// Scratch buffer to hold the <see cref="char"/>s to decode. Array must be large enough to hold
/// <paramref name="bufferOffset"/> and <paramref name="count"/> characters as well as Base64 padding
/// characters. Content is not preserved.
/// </param>
/// <param name="bufferOffset">
/// The offset into <paramref name="buffer"/> at which to begin writing the <see cref="char"/>s to decode.
/// </param>
/// <param name="count">The number of characters in <paramref name="input"/> to decode.</param>
/// <returns>The base64url-decoded form of the <paramref name="input"/>.</returns>
/// <remarks>
/// The input must not contain any whitespace or padding characters.
/// Throws <see cref="FormatException"/> if the input is malformed.
/// </remarks>
public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
ValidateParameters(input.Length, nameof(input), offset, count);
if (bufferOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferOffset));
}
if (count == 0)
{
return EmptyBytes;
}
// Assumption: input is base64url encoded without padding and contains no whitespace.
var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
var arraySizeRequired = checked(count + paddingCharsToAdd);
Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4.");
if (buffer.Length - bufferOffset < arraySizeRequired)
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
EncoderResources.WebEncoders_InvalidCountOffsetOrLength,
nameof(count),
nameof(bufferOffset),
nameof(input)),
nameof(count));
}
// Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'.
var i = bufferOffset;
for (var j = offset; i - bufferOffset < count; i++, j++)
{
var ch = input[j];
if (ch == '-')
{
buffer[i] = '+';
}
else if (ch == '_')
{
buffer[i] = '/';
}
else
{
buffer[i] = ch;
}
}
// Add the padding characters back.
for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--)
{
buffer[i] = '=';
}
// Decode.
// If the caller provided invalid base64 chars, they'll be caught here.
return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired);
}
/// <summary>
/// Gets the minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters
/// with the <see cref="Base64UrlDecode(string, int, char[], int, int)"/> method.
/// </summary>
/// <param name="count">The number of characters to decode.</param>
/// <returns>
/// The minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters.
/// </returns>
public static int GetArraySizeRequiredToDecode(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (count == 0)
{
return 0;
}
var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
return checked(count + numPaddingCharsToAdd);
}
/// <summary>
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(byte[] input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
return Base64UrlEncode(input, offset: 0, count: input.Length);
}
/// <summary>
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
/// <param name="count">The number of bytes from <paramref name="input"/> to encode.</param>
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
public static string Base64UrlEncode(byte[] input, int offset, int count)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
ValidateParameters(input.Length, nameof(input), offset, count);
// Special-case empty input
if (count == 0)
{
return string.Empty;
}
var buffer = new char[GetArraySizeRequiredToEncode(count)];
var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count);
return new String(buffer, startIndex: 0, length: numBase64Chars);
}
/// <summary>
/// Encodes <paramref name="input"/> using base64url encoding.
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
/// <param name="output">
/// Buffer to receive the base64url-encoded form of <paramref name="input"/>. Array must be large enough to
/// hold <paramref name="outputOffset"/> characters and the full base64-encoded form of
/// <paramref name="input"/>, including padding characters.
/// </param>
/// <param name="outputOffset">
/// The offset into <paramref name="output"/> at which to begin writing the base64url-encoded form of
/// <paramref name="input"/>.
/// </param>
/// <param name="count">The number of <c>byte</c>s from <paramref name="input"/> to encode.</param>
/// <returns>
/// The number of characters written to <paramref name="output"/>, less any padding characters.
/// </returns>
public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
ValidateParameters(input.Length, nameof(input), offset, count);
if (outputOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(outputOffset));
}
var arraySizeRequired = GetArraySizeRequiredToEncode(count);
if (output.Length - outputOffset < arraySizeRequired)
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
EncoderResources.WebEncoders_InvalidCountOffsetOrLength,
nameof(count),
nameof(outputOffset),
nameof(output)),
nameof(count));
}
// Special-case empty input.
if (count == 0)
{
return 0;
}
// Use base64url encoding with no padding characters. See RFC 4648, Sec. 5.
// Start with default Base64 encoding.
var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset);
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
for (var i = outputOffset; i - outputOffset < numBase64Chars; i++)
{
var ch = output[i];
if (ch == '+')
{
output[i] = '-';
}
else if (ch == '/')
{
output[i] = '_';
}
else if (ch == '=')
{
// We've reached a padding character; truncate the remainder.
return i - outputOffset;
}
}
return numBase64Chars;
}
/// <summary>
/// Get the minimum output <c>char[]</c> size required for encoding <paramref name="count"/>
/// <see cref="byte"/>s with the <see cref="Base64UrlEncode(byte[], int, char[], int, int)"/> method.
/// </summary>
/// <param name="count">The number of characters to encode.</param>
/// <returns>
/// The minimum output <c>char[]</c> size required for encoding <paramref name="count"/> <see cref="byte"/>s.
/// </returns>
public static int GetArraySizeRequiredToEncode(int count)
{
var numWholeOrPartialInputBlocks = checked(count + 2) / 3;
return checked(numWholeOrPartialInputBlocks * 4);
}
private static int GetNumBase64PaddingCharsInString(string str)
{
// Assumption: input contains a well-formed base64 string with no whitespace.
// base64 guaranteed have 0 - 2 padding characters.
if (str[str.Length - 1] == '=')
{
if (str[str.Length - 2] == '=')
{
return 2;
}
return 1;
}
return 0;
}
private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength)
{
switch (inputLength % 4)
{
case 0:
return 0;
case 2:
return 2;
case 3:
return 1;
default:
throw new FormatException(
string.Format(
CultureInfo.CurrentCulture,
EncoderResources.WebEncoders_MalformedInput,
inputLength));
}
}
private static void ValidateParameters(int bufferLength, string inputName, int offset, int count)
{
if (offset < 0)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (bufferLength - offset < count)
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
EncoderResources.WebEncoders_InvalidCountOffsetOrLength,
nameof(count),
nameof(offset),
inputName),
nameof(count));
}
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.Extensions.CommandLineUtils
{
public class ArgumentEscaperTests
{
[Theory]
[InlineData(new[] { "one", "two", "three" }, "one two three")]
[InlineData(new[] { "line1\nline2", "word1\tword2" }, "\"line1\nline2\" \"word1\tword2\"")]
[InlineData(new[] { "with spaces" }, "\"with spaces\"")]
[InlineData(new[] { @"with\backslash" }, @"with\backslash")]
[InlineData(new[] { @"""quotedwith\backslash""" }, @"\""quotedwith\backslash\""")]
[InlineData(new[] { @"C:\Users\" }, @"C:\Users\")]
[InlineData(new[] { @"C:\Program Files\dotnet\" }, @"""C:\Program Files\dotnet\\""")]
[InlineData(new[] { @"backslash\""preceedingquote" }, @"backslash\\\""preceedingquote")]
public void EscapesArguments(string[] args, string expected)
{
Assert.Equal(expected, ArgumentEscaper.EscapeAndConcatenate(args));
}
}
}

View File

@ -0,0 +1,304 @@
// 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
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Certificates.Generation.Tests
{
public class CertificateManagerTests
{
public CertificateManagerTests(ITestOutputHelper output)
{
Output = output;
}
public const string TestCertificateSubject = "CN=aspnet.test";
public ITestOutputHelper Output { get; }
[Fact]
public void EnsureCreateHttpsCertificate_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.EnsureAspNetCoreHttpsDevelopmentCertificate(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 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))
{
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates;
foreach (var certificate in certificates)
{
Output.WriteLine($"Certificate: '{Convert.ToBase64String(certificate.Export(X509ContentType.Cert))}'.");
certificate.Dispose();
}
store.Close();
}
}
[Fact]
public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates()
{
// Arrange
const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx";
var certificatePassword = Guid.NewGuid().ToString();
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);
}
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
var httpsCertificate = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
// Act
var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(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(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
}
[Fact(Skip = "Requires user interaction")]
public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows()
{
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);
}
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
var trustFailed = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed);
}
[Fact(Skip = "Requires user interaction")]
public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates()
{
var manager = new CertificateManager();
DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
manager.CleanupHttpsCertificates(TestCertificateSubject);
Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
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());
}
}
}
#endif

View File

@ -0,0 +1,360 @@
// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class ClosedGenericMatcherTest
{
// queryType, interfaceType, expectedResult
public static TheoryData<Type, Type, Type> ExtractGenericInterfaceDataSet
{
get
{
return new TheoryData<Type, Type, Type>
{
// Closed generic types that match given open generic type.
{
typeof(IEnumerable<BaseClass>),
typeof(IEnumerable<>),
typeof(IEnumerable<BaseClass>)
},
{
typeof(IReadOnlyList<int>),
typeof(IReadOnlyList<>),
typeof(IReadOnlyList<int>)
},
{
typeof(KeyValuePair<string, object>),
typeof(KeyValuePair<,>),
typeof(KeyValuePair<string, object>)
},
// Closed generic interfaces that implement sub-interface of given open generic type.
{
typeof(ICollection<BaseClass>),
typeof(IEnumerable<>),
typeof(IEnumerable<BaseClass>)
},
{
typeof(IReadOnlyList<int>),
typeof(IEnumerable<>),
typeof(IEnumerable<int>)
},
{
typeof(IDictionary<string, object>),
typeof(IEnumerable<>),
typeof(IEnumerable<KeyValuePair<string, object>>)
},
// Class that implements closed generic based on given open generic interface.
{
typeof(BaseClass),
typeof(IDictionary<,>),
typeof(IDictionary<string, object>)
},
{
typeof(BaseClass),
typeof(IEquatable<>),
typeof(IEquatable<BaseClass>)
},
{
typeof(BaseClass),
typeof(ICollection<>),
typeof(ICollection<KeyValuePair<string, object>>)
},
// Derived class that implements closed generic based on given open generic interface.
{
typeof(DerivedClass),
typeof(IDictionary<,>),
typeof(IDictionary<string, object>)
},
{
typeof(DerivedClass),
typeof(IEquatable<>),
typeof(IEquatable<BaseClass>)
},
{
typeof(DerivedClass),
typeof(ICollection<>),
typeof(ICollection<KeyValuePair<string, object>>)
},
// Derived class that also implements another interface.
{
typeof(DerivedClassWithComparable),
typeof(IDictionary<,>),
typeof(IDictionary<string, object>)
},
{
typeof(DerivedClassWithComparable),
typeof(IEquatable<>),
typeof(IEquatable<BaseClass>)
},
{
typeof(DerivedClassWithComparable),
typeof(ICollection<>),
typeof(ICollection<KeyValuePair<string, object>>)
},
{
typeof(DerivedClassWithComparable),
typeof(IComparable<>),
typeof(IComparable<DerivedClassWithComparable>)
},
// Derived class using system implementation.
{
typeof(DerivedClassFromSystemImplementation),
typeof(ICollection<>),
typeof(ICollection<BaseClass>)
},
{
typeof(DerivedClassFromSystemImplementation),
typeof(IReadOnlyList<>),
typeof(IReadOnlyList<BaseClass>)
},
{
typeof(DerivedClassFromSystemImplementation),
typeof(IEnumerable<>),
typeof(IEnumerable<BaseClass>)
},
// Not given an open generic type.
{
typeof(IEnumerable<BaseClass>),
typeof(IEnumerable<BaseClass>),
null
},
{
typeof(IEnumerable<BaseClass>),
typeof(IEnumerable),
null
},
{
typeof(IReadOnlyList<int>),
typeof(BaseClass),
null
},
{
typeof(KeyValuePair<,>),
typeof(KeyValuePair<string, object>),
null
},
// Not a match.
{
typeof(IEnumerable<BaseClass>),
typeof(IReadOnlyList<>),
null
},
{
typeof(IList<int>),
typeof(IReadOnlyList<>),
null
},
{
typeof(IDictionary<string, object>),
typeof(KeyValuePair<,>),
null
},
};
}
}
[Theory]
[MemberData(nameof(ExtractGenericInterfaceDataSet))]
public void ExtractGenericInterface_ReturnsExpectedType(
Type queryType,
Type interfaceType,
Type expectedResult)
{
// Arrange & Act
var result = ClosedGenericMatcher.ExtractGenericInterface(queryType, interfaceType);
// Assert
Assert.Equal(expectedResult, result);
}
// IEnumerable<int> is preferred because it is defined on the more-derived type.
[Fact]
public void ExtractGenericInterface_MultipleDefinitionsInherited()
{
// Arrange
var type = typeof(TwoIEnumerableImplementationsInherited);
// Act
var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>));
// Sort
Assert.Equal(typeof(IEnumerable<int>), result);
}
// IEnumerable<int> is preferred because we sort by Ordinal on the full name.
[Fact]
public void ExtractGenericInterface_MultipleDefinitionsOnSameType()
{
// Arrange
var type = typeof(TwoIEnumerableImplementationsOnSameClass);
// Act
var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>));
// Sort
Assert.Equal(typeof(IEnumerable<int>), result);
}
private class TwoIEnumerableImplementationsOnSameClass : IEnumerable<string>, IEnumerable<int>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
throw new NotImplementedException();
}
}
private class TwoIEnumerableImplementationsInherited : List<int>, IEnumerable<string>
{
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
private class BaseClass : IDictionary<string, object>, IEquatable<BaseClass>
{
object IDictionary<string, object>.this[string key]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
int ICollection<KeyValuePair<string, object>>.Count
{
get
{
throw new NotImplementedException();
}
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
ICollection<string> IDictionary<string, object>.Keys
{
get
{
throw new NotImplementedException();
}
}
ICollection<object> IDictionary<string, object>.Values
{
get
{
throw new NotImplementedException();
}
}
public bool Equals(BaseClass other)
{
throw new NotImplementedException();
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
void IDictionary<string, object>.Add(string key, object value)
{
throw new NotImplementedException();
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
throw new NotImplementedException();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
bool IDictionary<string, object>.ContainsKey(string key)
{
throw new NotImplementedException();
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
throw new NotImplementedException();
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
bool IDictionary<string, object>.Remove(string key)
{
throw new NotImplementedException();
}
bool IDictionary<string, object>.TryGetValue(string key, out object value)
{
throw new NotImplementedException();
}
}
private class DerivedClass : BaseClass
{
}
private class DerivedClassWithComparable : DerivedClass, IComparable<DerivedClassWithComparable>
{
public int CompareTo(DerivedClassWithComparable other)
{
throw new NotImplementedException();
}
}
private class DerivedClassFromSystemImplementation : Collection<BaseClass>
{
}
}
}

View File

@ -0,0 +1,693 @@
// 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.Text;
using Microsoft.Extensions.CommandLineUtils;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class CommandLineApplicationTests
{
[Fact]
public void CommandNameCanBeMatched()
{
var called = false;
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.OnExecute(() =>
{
called = true;
return 5;
});
});
var result = app.Execute("test");
Assert.Equal(5, result);
Assert.True(called);
}
[Fact]
public void RemainingArgsArePassed()
{
CommandArgument first = null;
CommandArgument second = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Argument("first", "First argument");
second = c.Argument("second", "Second argument");
c.OnExecute(() => 0);
});
app.Execute("test", "one", "two");
Assert.Equal("one", first.Value);
Assert.Equal("two", second.Value);
}
[Fact]
public void ExtraArgumentCausesException()
{
CommandArgument first = null;
CommandArgument second = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Argument("first", "First argument");
second = c.Argument("second", "Second argument");
c.OnExecute(() => 0);
});
var ex = Assert.Throws<CommandParsingException>(() => app.Execute("test", "one", "two", "three"));
Assert.Contains("three", ex.Message);
}
[Fact]
public void UnknownCommandCausesException()
{
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.Argument("first", "First argument");
c.Argument("second", "Second argument");
c.OnExecute(() => 0);
});
var ex = Assert.Throws<CommandParsingException>(() => app.Execute("test2", "one", "two", "three"));
Assert.Contains("test2", ex.Message);
}
[Fact]
public void MultipleValuesArgumentConsumesAllArgumentValues()
{
CommandArgument argument = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
argument = c.Argument("arg", "Argument that allows multiple values", multipleValues: true);
c.OnExecute(() => 0);
});
app.Execute("test", "one", "two", "three", "four", "five");
Assert.Equal(new[] { "one", "two", "three", "four", "five" }, argument.Values);
}
[Fact]
public void MultipleValuesArgumentConsumesAllRemainingArgumentValues()
{
CommandArgument first = null;
CommandArgument second = null;
CommandArgument third = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Argument("first", "First argument");
second = c.Argument("second", "Second argument");
third = c.Argument("third", "Third argument that allows multiple values", multipleValues: true);
c.OnExecute(() => 0);
});
app.Execute("test", "one", "two", "three", "four", "five");
Assert.Equal("one", first.Value);
Assert.Equal("two", second.Value);
Assert.Equal(new[] { "three", "four", "five" }, third.Values);
}
[Fact]
public void MultipleValuesArgumentMustBeTheLastArgument()
{
var app = new CommandLineApplication();
app.Argument("first", "First argument", multipleValues: true);
var ex = Assert.Throws<InvalidOperationException>(() => app.Argument("second", "Second argument"));
Assert.Contains($"The last argument 'first' accepts multiple values. No more argument can be added.",
ex.Message);
}
[Fact]
public void OptionSwitchMayBeProvided()
{
CommandOption first = null;
CommandOption second = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Option("--first <NAME>", "First argument", CommandOptionType.SingleValue);
second = c.Option("--second <NAME>", "Second argument", CommandOptionType.SingleValue);
c.OnExecute(() => 0);
});
app.Execute("test", "--first", "one", "--second", "two");
Assert.Equal("one", first.Values[0]);
Assert.Equal("two", second.Values[0]);
}
[Fact]
public void OptionValueMustBeProvided()
{
CommandOption first = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Option("--first <NAME>", "First argument", CommandOptionType.SingleValue);
c.OnExecute(() => 0);
});
var ex = Assert.Throws<CommandParsingException>(() => app.Execute("test", "--first"));
Assert.Contains($"Missing value for option '{first.LongName}'", ex.Message);
}
[Fact]
public void ValuesMayBeAttachedToSwitch()
{
CommandOption first = null;
CommandOption second = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Option("--first <NAME>", "First argument", CommandOptionType.SingleValue);
second = c.Option("--second <NAME>", "Second argument", CommandOptionType.SingleValue);
c.OnExecute(() => 0);
});
app.Execute("test", "--first=one", "--second:two");
Assert.Equal("one", first.Values[0]);
Assert.Equal("two", second.Values[0]);
}
[Fact]
public void ShortNamesMayBeDefined()
{
CommandOption first = null;
CommandOption second = null;
var app = new CommandLineApplication();
app.Command("test", c =>
{
first = c.Option("-1 --first <NAME>", "First argument", CommandOptionType.SingleValue);
second = c.Option("-2 --second <NAME>", "Second argument", CommandOptionType.SingleValue);
c.OnExecute(() => 0);
});
app.Execute("test", "-1=one", "-2", "two");
Assert.Equal("one", first.Values[0]);
Assert.Equal("two", second.Values[0]);
}
[Fact]
public void ThrowsExceptionOnUnexpectedCommandOrArgumentByDefault()
{
var unexpectedArg = "UnexpectedArg";
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.OnExecute(() => 0);
});
var exception = Assert.Throws<CommandParsingException>(() => app.Execute("test", unexpectedArg));
Assert.Equal($"Unrecognized command or argument '{unexpectedArg}'", exception.Message);
}
[Fact]
public void AllowNoThrowBehaviorOnUnexpectedArgument()
{
var unexpectedArg = "UnexpectedArg";
var app = new CommandLineApplication();
var testCmd = app.Command("test", c =>
{
c.OnExecute(() => 0);
},
throwOnUnexpectedArg: false);
// (does not throw)
app.Execute("test", unexpectedArg);
var arg = Assert.Single(testCmd.RemainingArguments);
Assert.Equal(unexpectedArg, arg);
}
[Fact]
public void ThrowsExceptionOnUnexpectedLongOptionByDefault()
{
var unexpectedOption = "--UnexpectedOption";
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.OnExecute(() => 0);
});
var exception = Assert.Throws<CommandParsingException>(() => app.Execute("test", unexpectedOption));
Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message);
}
[Fact]
public void AllowNoThrowBehaviorOnUnexpectedLongOption()
{
var unexpectedOption = "--UnexpectedOption";
var app = new CommandLineApplication();
var testCmd = app.Command("test", c =>
{
c.OnExecute(() => 0);
},
throwOnUnexpectedArg: false);
// (does not throw)
app.Execute("test", unexpectedOption);
var arg = Assert.Single(testCmd.RemainingArguments);
Assert.Equal(unexpectedOption, arg);
}
[Fact]
public void ThrowsExceptionOnUnexpectedShortOptionByDefault()
{
var unexpectedOption = "-uexp";
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.OnExecute(() => 0);
});
var exception = Assert.Throws<CommandParsingException>(() => app.Execute("test", unexpectedOption));
Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message);
}
[Fact]
public void AllowNoThrowBehaviorOnUnexpectedShortOption()
{
var unexpectedOption = "-uexp";
var app = new CommandLineApplication();
var testCmd = app.Command("test", c =>
{
c.OnExecute(() => 0);
},
throwOnUnexpectedArg: false);
// (does not throw)
app.Execute("test", unexpectedOption);
var arg = Assert.Single(testCmd.RemainingArguments);
Assert.Equal(unexpectedOption, arg);
}
[Fact]
public void ThrowsExceptionOnUnexpectedSymbolOptionByDefault()
{
var unexpectedOption = "-?";
var app = new CommandLineApplication();
app.Command("test", c =>
{
c.OnExecute(() => 0);
});
var exception = Assert.Throws<CommandParsingException>(() => app.Execute("test", unexpectedOption));
Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message);
}
[Fact]
public void AllowNoThrowBehaviorOnUnexpectedSymbolOption()
{
var unexpectedOption = "-?";
var app = new CommandLineApplication();
var testCmd = app.Command("test", c =>
{
c.OnExecute(() => 0);
},
throwOnUnexpectedArg: false);
// (does not throw)
app.Execute("test", unexpectedOption);
var arg = Assert.Single(testCmd.RemainingArguments);
Assert.Equal(unexpectedOption, arg);
}
[Fact]
public void ThrowsExceptionOnUnexpectedOptionBeforeValidSubcommandByDefault()
{
var unexpectedOption = "--unexpected";
CommandLineApplication subCmd = null;
var app = new CommandLineApplication();
app.Command("k", c =>
{
subCmd = c.Command("run", _ => { });
c.OnExecute(() => 0);
});
var exception = Assert.Throws<CommandParsingException>(() => app.Execute("k", unexpectedOption, "run"));
Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message);
}
[Fact]
public void AllowNoThrowBehaviorOnUnexpectedOptionAfterSubcommand()
{
var unexpectedOption = "--unexpected";
CommandLineApplication subCmd = null;
var app = new CommandLineApplication();
var testCmd = app.Command("k", c =>
{
subCmd = c.Command("run", _ => { }, throwOnUnexpectedArg: false);
c.OnExecute(() => 0);
});
// (does not throw)
app.Execute("k", "run", unexpectedOption);
Assert.Empty(testCmd.RemainingArguments);
var arg = Assert.Single(subCmd.RemainingArguments);
Assert.Equal(unexpectedOption, arg);
}
[Fact]
public void OptionsCanBeInherited()
{
var app = new CommandLineApplication();
var optionA = app.Option("-a|--option-a", "", CommandOptionType.SingleValue, inherited: true);
string optionAValue = null;
var optionB = app.Option("-b", "", CommandOptionType.SingleValue, inherited: false);
var subcmd = app.Command("subcmd", c =>
{
c.OnExecute(() =>
{
optionAValue = optionA.Value();
return 0;
});
});
Assert.Equal(2, app.GetOptions().Count());
Assert.Single(subcmd.GetOptions());
app.Execute("-a", "A1", "subcmd");
Assert.Equal("A1", optionAValue);
Assert.Throws<CommandParsingException>(() => app.Execute("subcmd", "-b", "B"));
Assert.Contains("-a|--option-a", subcmd.GetHelpText());
}
[Fact]
public void NestedOptionConflictThrows()
{
var app = new CommandLineApplication();
app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: true);
app.Command("subcmd", c =>
{
c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue);
});
Assert.Throws<InvalidOperationException>(() => app.Execute("subcmd", "-a", "b"));
}
[Fact]
public void OptionsWithSameName()
{
var app = new CommandLineApplication();
var top = app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: false);
CommandOption nested = null;
app.Command("subcmd", c =>
{
nested = c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue);
});
app.Execute("-a", "top");
Assert.Equal("top", top.Value());
Assert.Null(nested.Value());
top.Values.Clear();
app.Execute("subcmd", "-a", "nested");
Assert.Null(top.Value());
Assert.Equal("nested", nested.Value());
}
[Fact]
public void NestedInheritedOptions()
{
string globalOptionValue = null, nest1OptionValue = null, nest2OptionValue = null;
var app = new CommandLineApplication();
CommandLineApplication subcmd2 = null;
var g = app.Option("-g|--global", "Global option", CommandOptionType.SingleValue, inherited: true);
var subcmd1 = app.Command("lvl1", s1 =>
{
var n1 = s1.Option("--nest1", "Nested one level down", CommandOptionType.SingleValue, inherited: true);
subcmd2 = s1.Command("lvl2", s2 =>
{
var n2 = s2.Option("--nest2", "Nested one level down", CommandOptionType.SingleValue, inherited: true);
s2.HelpOption("-h|--help");
s2.OnExecute(() =>
{
globalOptionValue = g.Value();
nest1OptionValue = n1.Value();
nest2OptionValue = n2.Value();
return 0;
});
});
});
Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest2");
Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest1");
Assert.Contains(app.GetOptions(), o => o.LongName == "global");
Assert.DoesNotContain(subcmd1.GetOptions(), o => o.LongName == "nest2");
Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "nest1");
Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "global");
Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest2");
Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest1");
Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "global");
Assert.Throws<CommandParsingException>(() => app.Execute("--nest2", "N2", "--nest1", "N1", "-g", "G"));
Assert.Throws<CommandParsingException>(() => app.Execute("lvl1", "--nest2", "N2", "--nest1", "N1", "-g", "G"));
app.Execute("lvl1", "lvl2", "--nest2", "N2", "-g", "G", "--nest1", "N1");
Assert.Equal("G", globalOptionValue);
Assert.Equal("N1", nest1OptionValue);
Assert.Equal("N2", nest2OptionValue);
}
[Theory]
[InlineData(new string[0], new string[0], null)]
[InlineData(new[] { "--" }, new string[0], null)]
[InlineData(new[] { "-t", "val" }, new string[0], "val")]
[InlineData(new[] { "-t", "val", "--" }, new string[0], "val")]
[InlineData(new[] { "--top", "val", "--", "a" }, new[] { "a" }, "val")]
[InlineData(new[] { "--", "a", "--top", "val" }, new[] { "a", "--top", "val" }, null)]
[InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "a", "--", "b" }, "val")]
[InlineData(new[] { "--", "--help" }, new[] { "--help" }, null)]
[InlineData(new[] { "--", "--version" }, new[] { "--version" }, null)]
public void ArgumentSeparator(string[] input, string[] expectedRemaining, string topLevelValue)
{
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
AllowArgumentSeparator = true
};
var optHelp = app.HelpOption("--help");
var optVersion = app.VersionOption("--version", "1", "1.0");
var optTop = app.Option("-t|--top <TOP>", "arg for command", CommandOptionType.SingleValue);
app.Execute(input);
Assert.Equal(topLevelValue, optTop.Value());
Assert.False(optHelp.HasValue());
Assert.False(optVersion.HasValue());
Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray());
}
[Fact]
public void HelpTextIgnoresHiddenItems()
{
var app = new CommandLineApplication()
{
Name = "ninja-app",
Description = "You can't see it until it is too late"
};
app.Command("star", c =>
{
c.Option("--points <p>", "How many", CommandOptionType.MultipleValue);
c.ShowInHelpText = false;
});
app.Option("--smile", "Be a nice ninja", CommandOptionType.NoValue, o => { o.ShowInHelpText = false; });
var a = app.Argument("name", "Pseudonym, of course");
a.ShowInHelpText = false;
var help = app.GetHelpText();
Assert.Contains("ninja-app", help);
Assert.DoesNotContain("--points", help);
Assert.DoesNotContain("--smile", help);
Assert.DoesNotContain("name", help);
}
[Fact]
public void HelpTextUsesHelpOptionName()
{
var app = new CommandLineApplication
{
Name = "superhombre"
};
app.HelpOption("--ayuda-me");
var help = app.GetHelpText();
Assert.Contains("--ayuda-me", help);
}
[Fact]
public void HelpTextShowsArgSeparator()
{
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
Name = "proxy-command",
AllowArgumentSeparator = true
};
app.HelpOption("-h|--help");
Assert.Contains("Usage: proxy-command [options] [[--] <arg>...]", app.GetHelpText());
}
[Fact]
public void HelpTextShowsExtendedHelp()
{
var app = new CommandLineApplication()
{
Name = "befuddle",
ExtendedHelpText = @"
Remarks:
This command is so confusing that I want to include examples in the help text.
Examples:
dotnet befuddle -- I Can Haz Confusion Arguments
"
};
Assert.Contains(app.ExtendedHelpText, app.GetHelpText());
}
[Theory]
[InlineData(new[] { "--version", "--flag" }, "1.0")]
[InlineData(new[] { "-V", "-f" }, "1.0")]
[InlineData(new[] { "--help", "--flag" }, "some flag")]
[InlineData(new[] { "-h", "-f" }, "some flag")]
public void HelpAndVersionOptionStopProcessing(string[] input, string expectedOutData)
{
using (var outWriter = new StringWriter())
{
var app = new CommandLineApplication { Out = outWriter };
app.HelpOption("-h --help");
app.VersionOption("-V --version", "1", "1.0");
var optFlag = app.Option("-f |--flag", "some flag", CommandOptionType.NoValue);
app.Execute(input);
outWriter.Flush();
var outData = outWriter.ToString();
Assert.Contains(expectedOutData, outData);
Assert.False(optFlag.HasValue());
}
}
// disable inaccurate analyzer error https://github.com/xunit/xunit/issues/1274
#pragma warning disable xUnit1010
#pragma warning disable xUnit1011
[Theory]
[InlineData("-f:File1", "-f:File2")]
[InlineData("--file:File1", "--file:File2")]
[InlineData("--file", "File1", "--file", "File2")]
#pragma warning restore xUnit1010
#pragma warning restore xUnit1011
public void ThrowsExceptionOnSingleValueOptionHavingTwoValues(params string[] inputOptions)
{
var app = new CommandLineApplication();
app.Option("-f |--file", "some file", CommandOptionType.SingleValue);
var exception = Assert.Throws<CommandParsingException>(() => app.Execute(inputOptions));
Assert.Equal("Unexpected value 'File2' for option 'file'", exception.Message);
}
[Theory]
[InlineData("-v")]
[InlineData("--verbose")]
public void NoValueOptionCanBeSet(string input)
{
var app = new CommandLineApplication();
var optVerbose = app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue);
app.Execute(input);
Assert.True(optVerbose.HasValue());
}
[Theory]
[InlineData("-v:true")]
[InlineData("--verbose:true")]
public void ThrowsExceptionOnNoValueOptionHavingValue(string inputOption)
{
var app = new CommandLineApplication();
app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue);
var exception = Assert.Throws<CommandParsingException>(() => app.Execute(inputOption));
Assert.Equal("Unexpected value 'true' for option 'verbose'", exception.Message);
}
[Fact]
public void ThrowsExceptionOnEmptyCommandOrArgument()
{
var inputOption = String.Empty;
var app = new CommandLineApplication();
var exception = Assert.Throws<CommandParsingException>(() => app.Execute(inputOption));
Assert.Equal($"Unrecognized command or argument '{inputOption}'", exception.Message);
}
[Fact]
public void ThrowsExceptionOnInvalidOption()
{
var inputOption = "-";
var app = new CommandLineApplication();
var exception = Assert.Throws<CommandParsingException>(() => app.Execute(inputOption));
Assert.Equal($"Unrecognized option '{inputOption}'", exception.Message);
}
}
}

View File

@ -0,0 +1,91 @@
// 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 Xunit;
namespace Microsoft.Extensions.Internal
{
public class CopyOnWriteDictionaryHolderTest
{
[Fact]
public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed()
{
// Arrange
var source = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "test-key", "test-value" },
{ "key2", "key2-value" }
};
var holder = new CopyOnWriteDictionaryHolder<string, object>(source);
// Act and Assert
Assert.Equal("key2-value", holder["key2"]);
Assert.Equal(2, holder.Count);
Assert.Equal(new string[] { "test-key", "key2" }, holder.Keys.ToArray());
Assert.Equal(new object[] { "test-value", "key2-value" }, holder.Values.ToArray());
Assert.True(holder.ContainsKey("test-key"));
object value;
Assert.False(holder.TryGetValue("different-key", out value));
Assert.False(holder.HasBeenCopied);
Assert.Same(source, holder.ReadDictionary);
}
[Fact]
public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged()
{
// Arrange
var source = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var holder = new CopyOnWriteDictionaryHolder<string, object>(source);
// Act
holder["key2"] = "value3";
// Assert
Assert.Equal("value2", source["key2"]);
Assert.Equal(2, holder.Count);
Assert.Equal("value1", holder["key1"]);
Assert.Equal("value3", holder["key2"]);
Assert.True(holder.HasBeenCopied);
Assert.NotSame(source, holder.ReadDictionary);
}
[Fact]
public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceValueIsAdded()
{
// Arrange
var source = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var holder = new CopyOnWriteDictionaryHolder<string, object>(source);
// Act
holder.Add("key3", "value3");
holder.Remove("key1");
// Assert
Assert.Equal(2, source.Count);
Assert.Equal("value1", source["key1"]);
Assert.Equal(2, holder.Count);
Assert.Equal("value2", holder["KeY2"]);
Assert.Equal("value3", holder["key3"]);
Assert.True(holder.HasBeenCopied);
Assert.NotSame(source, holder.ReadDictionary);
}
}
}

View File

@ -0,0 +1,109 @@
// 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 Moq;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class CopyOnWriteDictionaryTest
{
[Fact]
public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed()
{
// Arrange
var values = new List<object>();
var enumerator = Mock.Of<IEnumerator<KeyValuePair<string, object>>>();
var sourceDictionary = new Mock<IDictionary<string, object>>(MockBehavior.Strict);
sourceDictionary
.SetupGet(d => d.Count)
.Returns(100)
.Verifiable();
sourceDictionary
.SetupGet(d => d.Values)
.Returns(values)
.Verifiable();
sourceDictionary
.Setup(d => d.ContainsKey("test-key"))
.Returns(value: true)
.Verifiable();
sourceDictionary
.Setup(d => d.GetEnumerator())
.Returns(enumerator)
.Verifiable();
sourceDictionary
.Setup(d => d["key2"])
.Returns("key2-value")
.Verifiable();
object value;
sourceDictionary.Setup(d => d.TryGetValue("different-key", out value))
.Returns(false)
.Verifiable();
var copyOnWriteDictionary = new CopyOnWriteDictionary<string, object>(sourceDictionary.Object,
StringComparer.OrdinalIgnoreCase);
// Act and Assert
Assert.Equal("key2-value", copyOnWriteDictionary["key2"]);
Assert.Equal(100, copyOnWriteDictionary.Count);
Assert.Same(values, copyOnWriteDictionary.Values);
Assert.True(copyOnWriteDictionary.ContainsKey("test-key"));
Assert.Same(enumerator, copyOnWriteDictionary.GetEnumerator());
Assert.False(copyOnWriteDictionary.TryGetValue("different-key", out value));
sourceDictionary.Verify();
}
[Fact]
public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged()
{
// Arrange
var values = new List<object>();
var sourceDictionary = new Dictionary<string, object>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var copyOnWriteDictionary = new CopyOnWriteDictionary<string, object>(
sourceDictionary,
StringComparer.OrdinalIgnoreCase);
// Act
copyOnWriteDictionary["key2"] = "value3";
// Assert
Assert.Equal("value2", sourceDictionary["key2"]);
Assert.Equal(2, copyOnWriteDictionary.Count);
Assert.Equal("value1", copyOnWriteDictionary["key1"]);
Assert.Equal("value3", copyOnWriteDictionary["key2"]);
}
[Fact]
public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceDictionaryIsModified()
{
// Arrange
var values = new List<object>();
var sourceDictionary = new Dictionary<string, object>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var copyOnWriteDictionary = new CopyOnWriteDictionary<string, object>(
sourceDictionary,
StringComparer.OrdinalIgnoreCase);
// Act
copyOnWriteDictionary.Add("key3", "value3");
copyOnWriteDictionary.Remove("key1");
// Assert
Assert.Equal(2, sourceDictionary.Count);
Assert.Equal("value1", sourceDictionary["key1"]);
Assert.Equal(2, copyOnWriteDictionary.Count);
Assert.Equal("value2", copyOnWriteDictionary["KeY2"]);
Assert.Equal("value3", copyOnWriteDictionary["key3"]);
}
}
}

View File

@ -0,0 +1,24 @@
// 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
using System.IO;
using System.Runtime.InteropServices;
using Xunit;
namespace Microsoft.Extensions.CommandLineUtils
{
public class DotNetMuxerTests
{
[Fact]
public void FindsTheMuxer()
{
var muxerPath = DotNetMuxer.MuxerPath;
Assert.NotNull(muxerPath);
Assert.True(File.Exists(muxerPath), "The file did not exist");
Assert.True(Path.IsPathRooted(muxerPath), "The path should be rooted");
Assert.Equal("dotnet", Path.GetFileNameWithoutExtension(muxerPath), ignoreCase: true);
}
}
}
#endif

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class HashCodeCombinerTest
{
[Fact]
public void GivenTheSameInputs_ItProducesTheSameOutput()
{
var hashCode1 = new HashCodeCombiner();
var hashCode2 = new HashCodeCombiner();
hashCode1.Add(42);
hashCode1.Add("foo");
hashCode2.Add(42);
hashCode2.Add("foo");
Assert.Equal(hashCode1.CombinedHash, hashCode2.CombinedHash);
}
[Fact]
public void HashCode_Is_OrderSensitive()
{
var hashCode1 = HashCodeCombiner.Start();
var hashCode2 = HashCodeCombiner.Start();
hashCode1.Add(42);
hashCode1.Add("foo");
hashCode2.Add("foo");
hashCode2.Add(42);
Assert.NotEqual(hashCode1.CombinedHash, hashCode2.CombinedHash);
}
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<DebugType>portable</DebugType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\src\**\*.cs"
Exclude="
..\src\BenchmarkRunner\**\*.cs;
..\src\RazorViews\**\*.cs;
..\src\StackTrace\ExceptionDetails\**\*.cs;
" />
<EmbeddedResource Include="..\src\**\*.resx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\testassets\ThrowingLibrary\ThrowingLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="FSharp.Core" />
<Reference Include="System.Reflection.Metadata" />
<Reference Include="System.Threading.Tasks.Extensions" />
<Reference Include="System.Security.Cryptography.Cng" />
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
<Reference Include="System.IO.Pipelines" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,634 @@
// 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.FSharp.Control;
using Microsoft.FSharp.Core;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class ObjectMethodExecutorTest
{
private TestObject _targetObject = new TestObject();
private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo();
[Fact]
public void ExecuteValueMethod()
{
var executor = GetExecutorForMethod("ValueMethod");
var result = executor.Execute(
_targetObject,
new object[] { 10, 20 });
Assert.False(executor.IsMethodAsync);
Assert.Equal(30, (int)result);
}
[Fact]
public void ExecuteVoidValueMethod()
{
var executor = GetExecutorForMethod("VoidValueMethod");
var result = executor.Execute(
_targetObject,
new object[] { 10 });
Assert.False(executor.IsMethodAsync);
Assert.Null(result);
}
[Fact]
public void ExecuteValueMethodWithReturnType()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnType");
var result = executor.Execute(
_targetObject,
new object[] { 10 });
var resultObject = Assert.IsType<TestObject>(result);
Assert.False(executor.IsMethodAsync);
Assert.Equal("Hello", resultObject.value);
}
[Fact]
public void ExecuteValueMethodUpdateValue()
{
var executor = GetExecutorForMethod("ValueMethodUpdateValue");
var parameter = new TestObject();
var result = executor.Execute(
_targetObject,
new object[] { parameter });
var resultObject = Assert.IsType<TestObject>(result);
Assert.False(executor.IsMethodAsync);
Assert.Equal("HelloWorld", resultObject.value);
}
[Fact]
public void ExecuteValueMethodWithReturnTypeThrowsException()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException");
var parameter = new TestObject();
Assert.False(executor.IsMethodAsync);
Assert.Throws<NotImplementedException>(
() => executor.Execute(
_targetObject,
new object[] { parameter }));
}
[Fact]
public async Task ExecuteValueMethodAsync()
{
var executor = GetExecutorForMethod("ValueMethodAsync");
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { 10, 20 });
Assert.True(executor.IsMethodAsync);
Assert.Equal(30, (int)result);
}
[Fact]
public async Task ExecuteValueMethodWithReturnTypeAsync()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync");
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { 10 });
var resultObject = Assert.IsType<TestObject>(result);
Assert.True(executor.IsMethodAsync);
Assert.Equal("Hello", resultObject.value);
}
[Fact]
public async Task ExecuteValueMethodUpdateValueAsync()
{
var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync");
var parameter = new TestObject();
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { parameter });
var resultObject = Assert.IsType<TestObject>(result);
Assert.True(executor.IsMethodAsync);
Assert.Equal("HelloWorld", resultObject.value);
}
[Fact]
public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync");
var parameter = new TestObject();
Assert.True(executor.IsMethodAsync);
await Assert.ThrowsAsync<NotImplementedException>(
async () => await executor.ExecuteAsync(
_targetObject,
new object[] { parameter }));
}
[Fact]
public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync");
var parameter = new TestObject();
Assert.True(executor.IsMethodAsync);
await Assert.ThrowsAsync<NotImplementedException>(
async () => await executor.ExecuteAsync(
_targetObject,
new object[] { parameter }));
}
[Fact]
public void GetDefaultValueForParameters_ReturnsSuppliedValues()
{
var suppliedDefaultValues = new object[] { 123, "test value" };
var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues);
Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0));
Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1));
Assert.Throws<ArgumentOutOfRangeException>(() => executor.GetDefaultValueForParameter(2));
}
[Fact]
public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied()
{
var executor = GetExecutorForMethod("MethodWithMultipleParameters");
Assert.Throws<InvalidOperationException>(() => executor.GetDefaultValueForParameter(0));
}
[Fact]
public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync");
// Act
var result = await (TestAwaitable<TestObject>)executor.Execute(_targetObject, new object[] { "Hello", 123 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(TestObject), executor.AsyncResultType);
Assert.NotNull(result);
Assert.Equal("Hello 123", result.value);
}
[Fact]
public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync");
// Act
var result = await (TestAwaitable<int>)executor.Execute(_targetObject, new object[] { 123, 456 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(int), executor.AsyncResultType);
Assert.Equal(579, result);
}
[Fact]
public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(TestObject), executor.AsyncResultType);
Assert.NotNull(result);
Assert.IsType<TestObject>(result);
Assert.Equal("Hello 123", ((TestObject)result).value);
}
[Fact]
public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(int), executor.AsyncResultType);
Assert.NotNull(result);
Assert.IsType<int>(result);
Assert.Equal(579, (int)result);
}
[Fact]
public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("VoidValueMethodAsync");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(void), executor.AsyncResultType);
Assert.Null(result);
}
[Fact]
public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[0]);
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("Used UnsafeOnCompleted", (string)result);
}
[Fact]
public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted()
{
// Arrange
var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[0]);
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("Used OnCompleted", (string)result);
}
[Fact]
public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("ValueTaskOfValueType");
// Act
var result = await (ValueTask<int>)executor.Execute(_targetObject, new object[] { 123 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(int), executor.AsyncResultType);
Assert.Equal(123, result);
}
[Fact]
public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("ValueTaskOfReferenceType");
// Act
var result = await (ValueTask<string>)executor.Execute(_targetObject, new object[] { "test result" });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("test result", result);
}
[Fact]
public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("ValueTaskOfValueType");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(int), executor.AsyncResultType);
Assert.NotNull(result);
Assert.Equal(123, (int)result);
}
[Fact]
public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("ValueTaskOfReferenceType");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("test result", result);
}
[Fact]
public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("FSharpAsyncMethod");
// Act
var fsharpAsync = (FSharpAsync<string>)executor.Execute(_targetObject, new object[] { "test result" });
var result = await FSharpAsync.StartAsTask(fsharpAsync,
FSharpOption<TaskCreationOptions>.None,
FSharpOption<CancellationToken>.None);
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("test result", result);
}
[Fact]
public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute()
{
// Arrange
var executor = GetExecutorForMethod("FSharpAsyncFailureMethod");
// Act
var fsharpAsync = (FSharpAsync<string>)executor.Execute(_targetObject, new object[] { "test result" });
var resultTask = FSharpAsync.StartAsTask(fsharpAsync,
FSharpOption<TaskCreationOptions>.None,
FSharpOption<CancellationToken>.None);
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
var exception = await Assert.ThrowsAsync<AggregateException>(async () => await resultTask);
Assert.IsType<InvalidOperationException>(exception.InnerException);
Assert.Equal("Test exception", exception.InnerException.Message);
}
[Fact]
public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("FSharpAsyncMethod");
// Act
var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
Assert.Equal("test result", result);
}
[Fact]
public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync()
{
// Arrange
var executor = GetExecutorForMethod("FSharpAsyncFailureMethod");
// Act
var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" });
// Assert
Assert.True(executor.IsMethodAsync);
Assert.Same(typeof(string), executor.AsyncResultType);
var exception = await Assert.ThrowsAsync<AggregateException>(async () => await resultTask);
Assert.IsType<InvalidOperationException>(exception.InnerException);
Assert.Equal("Test exception", exception.InnerException.Message);
}
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
{
var method = typeof(TestObject).GetMethod(methodName);
return ObjectMethodExecutor.Create(method, targetTypeInfo);
}
private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues)
{
var method = typeof(TestObject).GetMethod(methodName);
return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues);
}
public class TestObject
{
public string value;
public int ValueMethod(int i, int j)
{
return i + j;
}
public void VoidValueMethod(int i)
{
}
public TestObject ValueMethodWithReturnType(int i)
{
return new TestObject() { value = "Hello" }; ;
}
public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i)
{
throw new NotImplementedException("Not Implemented Exception");
}
public TestObject ValueMethodUpdateValue(TestObject parameter)
{
parameter.value = "HelloWorld";
return parameter;
}
public Task<int> ValueMethodAsync(int i, int j)
{
return Task.FromResult<int>(i + j);
}
public async Task VoidValueMethodAsync(int i)
{
await ValueMethodAsync(3, 4);
}
public Task<TestObject> ValueMethodWithReturnTypeAsync(int i)
{
return Task.FromResult<TestObject>(new TestObject() { value = "Hello" });
}
public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i)
{
await Task.CompletedTask;
throw new NotImplementedException("Not Implemented Exception");
}
public async Task<TestObject> ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i)
{
await Task.CompletedTask;
throw new NotImplementedException("Not Implemented Exception");
}
public Task<TestObject> ValueMethodUpdateValueAsync(TestObject parameter)
{
parameter.value = "HelloWorld";
return Task.FromResult<TestObject>(parameter);
}
public TestAwaitable<TestObject> CustomAwaitableOfReferenceTypeAsync(
string input1,
int input2)
{
return new TestAwaitable<TestObject>(new TestObject
{
value = $"{input1} {input2}"
});
}
public TestAwaitable<int> CustomAwaitableOfValueTypeAsync(
int input1,
int input2)
{
return new TestAwaitable<int>(input1 + input2);
}
public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion()
{
return new TestAwaitableWithICriticalNotifyCompletion();
}
public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion()
{
return new TestAwaitableWithoutICriticalNotifyCompletion();
}
public ValueTask<int> ValueTaskOfValueType(int result)
{
return new ValueTask<int>(result);
}
public ValueTask<string> ValueTaskOfReferenceType(string result)
{
return new ValueTask<string>(result);
}
public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam)
{
}
public FSharpAsync<string> FSharpAsyncMethod(string parameter)
{
return FSharpAsync.AwaitTask(Task.FromResult(parameter));
}
public FSharpAsync<string> FSharpAsyncFailureMethod(string parameter)
{
return FSharpAsync.AwaitTask(
Task.FromException<string>(new InvalidOperationException("Test exception")));
}
}
public class TestAwaitable<T>
{
private T _result;
private bool _isCompleted;
private List<Action> _onCompletedCallbacks = new List<Action>();
public TestAwaitable(T result)
{
_result = result;
// Simulate a brief delay before completion
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(100);
SetCompleted();
});
}
private void SetCompleted()
{
_isCompleted = true;
foreach (var callback in _onCompletedCallbacks)
{
callback();
}
}
public TestAwaiter GetAwaiter()
{
return new TestAwaiter(this);
}
public struct TestAwaiter : INotifyCompletion
{
private TestAwaitable<T> _owner;
public TestAwaiter(TestAwaitable<T> owner) : this()
{
_owner = owner;
}
public bool IsCompleted => _owner._isCompleted;
public void OnCompleted(Action continuation)
{
if (_owner._isCompleted)
{
continuation();
}
else
{
_owner._onCompletedCallbacks.Add(continuation);
}
}
public T GetResult()
{
return _owner._result;
}
}
}
public class TestAwaitableWithICriticalNotifyCompletion
{
public TestAwaiterWithICriticalNotifyCompletion GetAwaiter()
=> new TestAwaiterWithICriticalNotifyCompletion();
}
public class TestAwaitableWithoutICriticalNotifyCompletion
{
public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter()
=> new TestAwaiterWithoutICriticalNotifyCompletion();
}
public class TestAwaiterWithICriticalNotifyCompletion
: CompletionTrackingAwaiterBase, ICriticalNotifyCompletion
{
}
public class TestAwaiterWithoutICriticalNotifyCompletion
: CompletionTrackingAwaiterBase, INotifyCompletion
{
}
public class CompletionTrackingAwaiterBase
{
private string _result;
public bool IsCompleted { get; private set; }
public string GetResult() => _result;
public void OnCompleted(Action continuation)
{
_result = "Used OnCompleted";
IsCompleted = true;
continuation();
}
public void UnsafeOnCompleted(Action continuation)
{
_result = "Used UnsafeOnCompleted";
IsCompleted = true;
continuation();
}
}
}
}

View File

@ -0,0 +1,187 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class PropertyActivatorTest
{
[Fact]
public void Activate_InvokesValueAccessorWithExpectedValue()
{
// Arrange
var instance = new TestClass();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("IntProperty");
var invokedWith = -1;
var activator = new PropertyActivator<int>(
property,
valueAccessor: (val) =>
{
invokedWith = val;
return val;
});
// Act
activator.Activate(instance, 123);
// Assert
Assert.Equal(123, invokedWith);
}
[Fact]
public void Activate_SetsPropertyValue()
{
// Arrange
var instance = new TestClass();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("IntProperty");
var activator = new PropertyActivator<int>(property, valueAccessor: (val) => val + 1);
// Act
activator.Activate(instance, 123);
// Assert
Assert.Equal(124, instance.IntProperty);
}
[Fact]
public void GetPropertiesToActivate_RestrictsActivatableProperties()
{
// Arrange
var instance = new TestClass();
var typeInfo = instance.GetType().GetTypeInfo();
var expectedPropertyInfo = typeInfo.GetDeclaredProperty("ActivatableProperty");
// Act
var propertiesToActivate = PropertyActivator<int>.GetPropertiesToActivate(
type: typeof(TestClass),
activateAttributeType: typeof(TestActivateAttribute),
createActivateInfo:
(propertyInfo) => new PropertyActivator<int>(propertyInfo, valueAccessor: (val) => val + 1));
// Assert
Assert.Collection(
propertiesToActivate,
(activator) =>
{
Assert.Equal(expectedPropertyInfo, activator.PropertyInfo);
});
}
[Fact]
public void GetPropertiesToActivate_CanCreateCustomPropertyActivators()
{
// Arrange
var instance = new TestClass();
var typeInfo = instance.GetType().GetTypeInfo();
var expectedPropertyInfo = typeInfo.GetDeclaredProperty("IntProperty");
// Act
var propertiesToActivate = PropertyActivator<int>.GetPropertiesToActivate(
type: typeof(TestClass),
activateAttributeType: typeof(TestActivateAttribute),
createActivateInfo:
(propertyInfo) => new PropertyActivator<int>(expectedPropertyInfo, valueAccessor: (val) => val + 1));
// Assert
Assert.Collection(
propertiesToActivate,
(activator) =>
{
Assert.Equal(expectedPropertyInfo, activator.PropertyInfo);
});
}
[Fact]
public void GetPropertiesToActivate_ExcludesNonPublic()
{
// Arrange
var instance = new TestClassWithPropertyVisiblity();
var typeInfo = instance.GetType().GetTypeInfo();
var expectedPropertyInfo = typeInfo.GetDeclaredProperty("Public");
// Act
var propertiesToActivate = PropertyActivator<int>.GetPropertiesToActivate(
typeof(TestClassWithPropertyVisiblity),
typeof(TestActivateAttribute),
(propertyInfo) => new PropertyActivator<int>(propertyInfo, valueAccessor: (val) => val));
// Assert
Assert.Single(propertiesToActivate);
Assert.Single(propertiesToActivate, p => p.PropertyInfo == expectedPropertyInfo);
}
[Fact]
public void GetPropertiesToActivate_IncludesNonPublic()
{
// Arrange
var instance = new TestClassWithPropertyVisiblity();
var typeInfo = instance.GetType().GetTypeInfo();
// Act
var propertiesToActivate = PropertyActivator<int>.GetPropertiesToActivate(
typeof(TestClassWithPropertyVisiblity),
typeof(TestActivateAttribute),
(propertyInfo) => new PropertyActivator<int>(propertyInfo, valueAccessor: (val) => val),
includeNonPublic: true);
// Assert
Assert.Equal(5, propertiesToActivate.Length);
}
private class TestClass
{
public int IntProperty { get; set; }
[TestActivate]
public int ActivatableProperty { get; set; }
[TestActivate]
public int NoSetterActivatableProperty { get; }
[TestActivate]
public int this[int something] // Not activatable
{
get
{
return 0;
}
}
[TestActivate]
public static int StaticActivatablProperty { get; set; }
}
private class TestClassWithPropertyVisiblity
{
[TestActivate]
public int Public { get; set; }
[TestActivate]
protected int Protected { get; set; }
[TestActivate]
internal int Internal { get; set; }
[TestActivate]
protected internal int ProtectedInternal {get; set; }
[TestActivate]
private int Private { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
private class TestActivateAttribute : Attribute
{
}
private class ActivationInfo
{
public string Name { get; set; }
}
}
}

View File

@ -0,0 +1,831 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class PropertyHelperTest
{
[Fact]
public void PropertyHelper_ReturnsNameCorrectly()
{
// Arrange
var anonymous = new { foo = "bar" };
var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property;
// Act
var helper = new PropertyHelper(property);
// Assert
Assert.Equal("foo", property.Name);
Assert.Equal("foo", helper.Name);
}
[Fact]
public void PropertyHelper_ReturnsValueCorrectly()
{
// Arrange
var anonymous = new { bar = "baz" };
var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property;
// Act
var helper = new PropertyHelper(property);
// Assert
Assert.Equal("bar", helper.Name);
Assert.Equal("baz", helper.GetValue(anonymous));
}
[Fact]
public void PropertyHelper_ReturnsGetterDelegate()
{
// Arrange
var anonymous = new { bar = "baz" };
var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property;
// Act
var helper = new PropertyHelper(property);
// Assert
Assert.NotNull(helper.ValueGetter);
Assert.Equal("baz", helper.ValueGetter(anonymous));
}
[Fact]
public void SetValue_SetsPropertyValue()
{
// Arrange
var expected = "new value";
var instance = new BaseClass { PropA = "old value" };
var helper = PropertyHelper.GetProperties(
instance.GetType()).First(prop => prop.Name == "PropA");
// Act
helper.SetValue(instance, expected);
// Assert
Assert.Equal(expected, instance.PropA);
}
[Fact]
public void PropertyHelper_ReturnsSetterDelegate()
{
// Arrange
var expected = "new value";
var instance = new BaseClass { PropA = "old value" };
var helper = PropertyHelper.GetProperties(
instance.GetType()).First(prop => prop.Name == "PropA");
// Act and Assert
Assert.NotNull(helper.ValueSetter);
helper.ValueSetter(instance, expected);
// Assert
Assert.Equal(expected, instance.PropA);
}
[Fact]
public void PropertyHelper_ReturnsValueCorrectly_ForValueTypes()
{
// Arrange
var anonymous = new { foo = 32 };
var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property;
// Act
var helper = new PropertyHelper(property);
// Assert
Assert.Equal("foo", helper.Name);
Assert.Equal(32, helper.GetValue(anonymous));
}
[Fact]
public void PropertyHelper_ReturnsCachedPropertyHelper()
{
// Arrange
var anonymous = new { foo = "bar" };
// Act
var helpers1 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo());
var helpers2 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo());
// Assert
Assert.Single(helpers1);
Assert.Same(helpers1, helpers2);
Assert.Same(helpers1[0], helpers2[0]);
}
[Fact]
public void PropertyHelper_DoesNotChangeUnderscores()
{
// Arrange
var anonymous = new { bar_baz2 = "foo" };
// Act + Assert
var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()));
Assert.Equal("bar_baz2", helper.Name);
}
[Fact]
public void PropertyHelper_DoesNotFindPrivateProperties()
{
// Arrange
var anonymous = new PrivateProperties();
// Act + Assert
var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()));
Assert.Equal("Prop1", helper.Name);
}
[Fact]
public void PropertyHelper_DoesNotFindStaticProperties()
{
// Arrange
var anonymous = new Static();
// Act + Assert
var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()));
Assert.Equal("Prop5", helper.Name);
}
[Fact]
public void PropertyHelper_DoesNotFindSetOnlyProperties()
{
// Arrange
var anonymous = new SetOnly();
// Act + Assert
var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()));
Assert.Equal("Prop6", helper.Name);
}
[Theory]
[InlineData(typeof(int?))]
[InlineData(typeof(DayOfWeek?))]
public void PropertyHelper_WorksForNullablePrimitiveAndEnumTypes(Type nullableType)
{
// Act
var properties = PropertyHelper.GetProperties(nullableType);
// Assert
Assert.Empty(properties);
}
[Fact]
public void PropertyHelper_UnwrapsNullableTypes()
{
// Arrange
var myType = typeof(MyStruct?);
// Act
var properties = PropertyHelper.GetProperties(myType);
// Assert
var property = Assert.Single(properties);
Assert.Equal("Foo", property.Name);
}
[Fact]
public void PropertyHelper_WorksForStruct()
{
// Arrange
var anonymous = new MyProperties();
anonymous.IntProp = 3;
anonymous.StringProp = "Five";
// Act + Assert
var helper1 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "IntProp"));
var helper2 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "StringProp"));
Assert.Equal(3, helper1.GetValue(anonymous));
Assert.Equal("Five", helper2.GetValue(anonymous));
}
[Fact]
public void PropertyHelper_ForDerivedClass()
{
// Arrange
var derived = new DerivedClass { PropA = "propAValue", PropB = "propBValue" };
// Act
var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray();
// Assert
Assert.NotNull(helpers);
Assert.Equal(2, helpers.Length);
var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA"));
var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB"));
Assert.Equal("propAValue", propAHelper.GetValue(derived));
Assert.Equal("propBValue", propBHelper.GetValue(derived));
}
[Fact]
public void PropertyHelper_ForDerivedClass_WithNew()
{
// Arrange
var derived = new DerivedClassWithNew { PropA = "propAValue" };
// Act
var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray();
// Assert
Assert.NotNull(helpers);
Assert.Equal(2, helpers.Length);
var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA"));
var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB"));
Assert.Equal("propAValue", propAHelper.GetValue(derived));
Assert.Equal("Newed", propBHelper.GetValue(derived));
}
[Fact]
public void PropertyHelper_ForDerived_WithVirtual()
{
// Arrange
var derived = new DerivedClassWithOverride { PropA = "propAValue", PropB = "propBValue" };
// Act
var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray();
// Assert
Assert.NotNull(helpers);
Assert.Equal(2, helpers.Length);
var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA"));
var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB"));
Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived));
Assert.Equal("propBValue", propBHelper.GetValue(derived));
}
[Fact]
public void PropertyHelper_ForInterface_ReturnsExpectedProperties()
{
// Arrange
var expectedNames = new[] { "Count", "IsReadOnly" };
// Act
var helpers = PropertyHelper.GetProperties(typeof(ICollection<string>));
// Assert
Assert.Collection(
helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal),
helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); },
helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); });
}
[Fact]
public void PropertyHelper_ForDerivedInterface_ReturnsAllProperties()
{
// Arrange
var expectedNames = new[] { "Count", "IsReadOnly", "Keys", "Values" };
// Act
var helpers = PropertyHelper.GetProperties(typeof(IDictionary<string, string>));
// Assert
Assert.Collection(
helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal),
helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); },
helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); },
helper => { Assert.Equal(expectedNames[2], helper.Name, StringComparer.Ordinal); },
helper => { Assert.Equal(expectedNames[3], helper.Name, StringComparer.Ordinal); });
}
[Fact]
public void GetProperties_ExcludesIndexersAndPropertiesWithoutPublicGetters()
{
// Arrange
var type = typeof(DerivedClassWithNonReadableProperties);
// Act
var result = PropertyHelper.GetProperties(type).ToArray();
// Assert
Assert.Equal(3, result.Length);
Assert.Equal("Visible", result[0].Name);
Assert.Equal("PropA", result[1].Name);
Assert.Equal("PropB", result[2].Name);
}
[Fact]
public void GetVisibleProperties_NoHiddenProperty()
{
// Arrange
var type = typeof(string);
// Act
var result = PropertyHelper.GetVisibleProperties(type).ToArray();
// Assert
var property = Assert.Single(result);
Assert.Equal("Length", property.Name);
Assert.Equal(typeof(int), property.Property.PropertyType);
}
[Fact]
public void GetVisibleProperties_HiddenProperty()
{
// Arrange
var type = typeof(DerivedHiddenProperty);
// Act
var result = PropertyHelper.GetVisibleProperties(type).ToArray();
// Assert
Assert.Equal(2, result.Length);
Assert.Equal("Id", result[0].Name);
Assert.Equal(typeof(string), result[0].Property.PropertyType);
Assert.Equal("Name", result[1].Name);
Assert.Equal(typeof(string), result[1].Property.PropertyType);
}
[Fact]
public void GetVisibleProperties_HiddenProperty_TwoLevels()
{
// Arrange
var type = typeof(DerivedHiddenProperty2);
// Act
var result = PropertyHelper.GetVisibleProperties(type).ToArray();
// Assert
Assert.Equal(2, result.Length);
Assert.Equal("Id", result[0].Name);
Assert.Equal(typeof(Guid), result[0].Property.PropertyType);
Assert.Equal("Name", result[1].Name);
Assert.Equal(typeof(string), result[1].Property.PropertyType);
}
[Fact]
public void GetVisibleProperties_NoHiddenPropertyWithTypeInfoInput()
{
// Arrange
var type = typeof(string);
// Act
var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray();
// Assert
var property = Assert.Single(result);
Assert.Equal("Length", property.Name);
Assert.Equal(typeof(int), property.Property.PropertyType);
}
[Fact]
public void GetVisibleProperties_HiddenPropertyWithTypeInfoInput()
{
// Arrange
var type = typeof(DerivedHiddenProperty);
// Act
var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray();
// Assert
Assert.Equal(2, result.Length);
Assert.Equal("Id", result[0].Name);
Assert.Equal(typeof(string), result[0].Property.PropertyType);
Assert.Equal("Name", result[1].Name);
Assert.Equal(typeof(string), result[1].Property.PropertyType);
}
[Fact]
public void GetVisibleProperties_HiddenProperty_TwoLevelsWithTypeInfoInput()
{
// Arrange
var type = typeof(DerivedHiddenProperty2);
// Act
var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray();
// Assert
Assert.Equal(2, result.Length);
Assert.Equal("Id", result[0].Name);
Assert.Equal(typeof(Guid), result[0].Property.PropertyType);
Assert.Equal("Name", result[1].Name);
Assert.Equal(typeof(string), result[1].Property.PropertyType);
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties()
{
// Arrange
var instance = new BaseClass();
var typeInfo = instance.GetType().GetTypeInfo();
var publicProperty = typeInfo.GetDeclaredProperty("PropA");
var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected");
var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty);
var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty);
// Act
publicPropertySetter(instance, "TestPublic");
protectedPropertySetter(instance, "TestProtected");
// Assert
Assert.Equal("TestPublic", instance.PropA);
Assert.Equal("TestProtected", instance.GetPropProtected());
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties()
{
// Arrange
var instance = new DerivedClassWithOverride();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("PropA");
var propertySetter = PropertyHelper.MakeFastPropertySetter(property);
// Act
propertySetter(instance, "Test value");
// Assert
Assert.Equal("OverridenTest value", instance.PropA);
}
[Fact]
public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties()
{
// Arrange
var instance = new DerivedClassWithNew();
var typeInfo = instance.GetType().GetTypeInfo();
var property = typeInfo.GetDeclaredProperty("PropB");
var propertySetter = PropertyHelper.MakeFastPropertySetter(property);
// Act
propertySetter(instance, "Test value");
// Assert
Assert.Equal("NewedTest value", instance.PropB);
}
[Fact]
public void MakeFastPropertyGetter_ReferenceType_ForNullObject_Throws()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(BaseClass))
.Single(p => p.Name == nameof(BaseClass.PropA));
var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property);
// Act & Assert
Assert.Throws<NullReferenceException>(() => accessor(null));
}
[Fact]
public void MakeFastPropertyGetter_ValueType_ForNullObject_Throws()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(MyProperties))
.Single(p => p.Name == nameof(MyProperties.StringProp));
var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property);
// Act & Assert
Assert.Throws<NullReferenceException>(() => accessor(null));
}
[Fact]
public void MakeNullSafeFastPropertyGetter_ReferenceType_Success()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(BaseClass))
.Single(p => p.Name == nameof(BaseClass.PropA));
var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property);
// Act
var value = accessor(new BaseClass() { PropA = "Hi" });
// Assert
Assert.Equal("Hi", value);
}
[Fact]
public void MakeNullSafeFastPropertyGetter_ValueType_Success()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(MyProperties))
.Single(p => p.Name == nameof(MyProperties.StringProp));
var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property);
// Act
var value = accessor(new MyProperties() { StringProp = "Hi" });
// Assert
Assert.Equal("Hi", value);
}
[Fact]
public void MakeNullSafeFastPropertyGetter_ReferenceType_ForNullObject_ReturnsNull()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(BaseClass))
.Single(p => p.Name == nameof(BaseClass.PropA));
var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property);
// Act
var value = accessor(null);
// Assert
Assert.Null(value);
}
[Fact]
public void MakeNullSafeFastPropertyGetter_ValueType_ForNullObject_ReturnsNull()
{
// Arrange
var property = PropertyHelper
.GetProperties(typeof(MyProperties))
.Single(p => p.Name == nameof(MyProperties.StringProp));
var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property);
// Act
var value = accessor(null);
// Assert
Assert.Null(value);
}
public static TheoryData<object, KeyValuePair<string, object>> IgnoreCaseTestData
{
get
{
return new TheoryData<object, KeyValuePair<string, object>>
{
{
new
{
selected = true,
SeLeCtEd = false
},
new KeyValuePair<string, object>("selected", false)
},
{
new
{
SeLeCtEd = false,
selected = true
},
new KeyValuePair<string, object>("SeLeCtEd", true)
},
{
new
{
SelECTeD = false,
SeLECTED = true
},
new KeyValuePair<string, object>("SelECTeD", true)
}
};
}
}
[Theory]
[MemberData(nameof(IgnoreCaseTestData))]
public void ObjectToDictionary_IgnoresPropertyCase(object testObject,
KeyValuePair<string, object> expectedEntry)
{
// Act
var result = PropertyHelper.ObjectToDictionary(testObject);
// Assert
var entry = Assert.Single(result);
Assert.Equal(expectedEntry, entry);
}
[Fact]
public void ObjectToDictionary_WithNullObject_ReturnsEmptyDictionary()
{
// Arrange
object value = null;
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(0, dictValues.Count);
}
[Fact]
public void ObjectToDictionary_WithPlainObjectType_ReturnsEmptyDictionary()
{
// Arrange
var value = new object();
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(0, dictValues.Count);
}
[Fact]
public void ObjectToDictionary_WithPrimitiveType_LooksUpPublicProperties()
{
// Arrange
var value = "test";
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(1, dictValues.Count);
Assert.Equal(4, dictValues["Length"]);
}
[Fact]
public void ObjectToDictionary_WithAnonymousType_LooksUpProperties()
{
// Arrange
var value = new { test = "value", other = 1 };
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(2, dictValues.Count);
Assert.Equal("value", dictValues["test"]);
Assert.Equal(1, dictValues["other"]);
}
[Fact]
public void ObjectToDictionary_ReturnsCaseInsensitiveDictionary()
{
// Arrange
var value = new { TEST = "value", oThEr = 1 };
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(2, dictValues.Count);
Assert.Equal("value", dictValues["test"]);
Assert.Equal(1, dictValues["other"]);
}
[Fact]
public void ObjectToDictionary_ReturnsInheritedProperties()
{
// Arrange
var value = new ThreeDPoint() { X = 5, Y = 10, Z = 17 };
// Act
var dictValues = PropertyHelper.ObjectToDictionary(value);
// Assert
Assert.NotNull(dictValues);
Assert.Equal(3, dictValues.Count);
Assert.Equal(5, dictValues["X"]);
Assert.Equal(10, dictValues["Y"]);
Assert.Equal(17, dictValues["Z"]);
}
private class Point
{
public int X { get; set; }
public int Y { get; set; }
}
private class ThreeDPoint : Point
{
public int Z { get; set; }
}
private class Static
{
public static int Prop2 { get; set; }
public int Prop5 { get; set; }
}
private struct MyProperties
{
public int IntProp { get; set; }
public string StringProp { get; set; }
}
private class SetOnly
{
public int Prop2 { set { } }
public int Prop6 { get; set; }
}
private class PrivateProperties
{
public int Prop1 { get; set; }
protected int Prop2 { get; set; }
private int Prop3 { get; set; }
}
public class BaseClass
{
public string PropA { get; set; }
protected string PropProtected { get; set; }
public string GetPropProtected()
{
return PropProtected;
}
}
public class DerivedClass : BaseClass
{
public string PropB { get; set; }
}
public class BaseClassWithVirtual
{
public virtual string PropA { get; set; }
public string PropB { get; set; }
}
public class DerivedClassWithNew : BaseClassWithVirtual
{
private string _value = "Newed";
public new string PropB
{
get { return _value; }
set { _value = "Newed" + value; }
}
}
public class DerivedClassWithOverride : BaseClassWithVirtual
{
private string _value = "Overriden";
public override string PropA
{
get { return _value; }
set { _value = "Overriden" + value; }
}
}
private class DerivedClassWithNonReadableProperties : BaseClassWithVirtual
{
public string this[int index]
{
get { return string.Empty; }
set { }
}
public int Visible { get; set; }
private string NotVisible { get; set; }
public string NotVisible2 { private get; set; }
public string NotVisible3
{
set { }
}
public static string NotVisible4 { get; set; }
}
private struct MyStruct
{
public string Foo { get; set; }
}
private class BaseHiddenProperty
{
public int Id { get; set; }
}
private class DerivedHiddenProperty : BaseHiddenProperty
{
public new string Id { get; set; }
public string Name { get; set; }
}
private class DerivedHiddenProperty2 : DerivedHiddenProperty
{
public new Guid Id { get; set; }
public new string Name { get; private set; }
}
}
}

View File

@ -0,0 +1,4 @@
NOTE:
1. The tests for 'ExceptionDetailProvider' and 'StackTraceHelper' in project 'Microsoft.Extensions.StackTrace.Sources' are located in Diagnostics
repo. This is because they refer to some packages from FileSystem repo which causes a circular reference and breaks the
build.

View File

@ -0,0 +1,93 @@
// 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 System.Security.Claims;
using System.Security.Principal;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class SecurityHelperTests
{
[Fact]
public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity()
{
var user = SecurityHelper.MergeUserPrincipal(new ClaimsPrincipal(), new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), new string[0]));
Assert.NotNull(user);
Assert.Equal("Alpha", user.Identity.AuthenticationType);
Assert.Equal("Test1", user.Identity.Name);
Assert.IsAssignableFrom<ClaimsPrincipal>(user);
Assert.IsAssignableFrom<ClaimsIdentity>(user.Identity);
Assert.Single(user.Identities);
}
[Fact]
public void AddingExistingIdentityChangesDefaultButPreservesPrior()
{
ClaimsPrincipal user = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null);
Assert.Equal("Alpha", user.Identity.AuthenticationType);
Assert.Equal("Test1", user.Identity.Name);
user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test2", "Beta"), new string[0]));
Assert.Equal("Beta", user.Identity.AuthenticationType);
Assert.Equal("Test2", user.Identity.Name);
user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0]));
Assert.Equal("Gamma", user.Identity.AuthenticationType);
Assert.Equal("Test3", user.Identity.Name);
Assert.Equal(3, user.Identities.Count());
Assert.Equal("Test3", user.Identities.Skip(0).First().Name);
Assert.Equal("Test2", user.Identities.Skip(1).First().Name);
Assert.Equal("Test1", user.Identities.Skip(2).First().Name);
}
[Fact]
public void AddingPreservesNewIdentitiesAndDropsEmpty()
{
var existingPrincipal = new ClaimsPrincipal(new ClaimsIdentity());
var identityNoAuthTypeWithClaim = new ClaimsIdentity();
identityNoAuthTypeWithClaim.AddClaim(new Claim("identityNoAuthTypeWithClaim", "yes"));
existingPrincipal.AddIdentity(identityNoAuthTypeWithClaim);
var identityEmptyWithAuthType = new ClaimsIdentity("empty");
existingPrincipal.AddIdentity(identityEmptyWithAuthType);
Assert.False(existingPrincipal.Identity.IsAuthenticated);
var newPrincipal = new ClaimsPrincipal();
var newEmptyIdentity = new ClaimsIdentity();
var identityTwo = new ClaimsIdentity("yep");
newPrincipal.AddIdentity(newEmptyIdentity);
newPrincipal.AddIdentity(identityTwo);
var user = SecurityHelper.MergeUserPrincipal(existingPrincipal, newPrincipal);
// Preserve newPrincipal order
Assert.False(user.Identity.IsAuthenticated);
Assert.Null(user.Identity.Name);
Assert.Equal(4, user.Identities.Count());
Assert.Equal(newEmptyIdentity, user.Identities.Skip(0).First());
Assert.Equal(identityTwo, user.Identities.Skip(1).First());
Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First());
Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First());
// This merge should drop newEmptyIdentity since its empty
user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0]));
Assert.Equal("Gamma", user.Identity.AuthenticationType);
Assert.Equal("Test3", user.Identity.Name);
Assert.Equal(4, user.Identities.Count());
Assert.Equal("Test3", user.Identities.Skip(0).First().Name);
Assert.Equal(identityTwo, user.Identities.Skip(1).First());
Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First());
Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First());
}
}
}

View File

@ -0,0 +1,345 @@
// 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.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.StackTrace.Sources;
using ThrowingLibrary;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class StackTraceHelperTest
{
[Fact]
public void StackTraceHelper_IncludesLineNumbersForFiles()
{
// Arrange
Exception exception = null;
try
{
// Throwing an exception in the current assembly always seems to populate the full stack
// trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used
// on desktop.
Thrower.Throw();
}
catch (Exception ex)
{
exception = ex;
}
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
Assert.Collection(stackFrames,
frame =>
{
Assert.Contains("Thrower.cs", frame.FilePath);
Assert.Equal(17, frame.LineNumber);
},
frame =>
{
Assert.Contains("StackTraceHelperTest.cs", frame.FilePath);
});
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods()
{
// Arrange
var exception = Record.Exception(() => GenericMethod<string>(null));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod<T>(T val)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters()
{
// Arrange
var exception = Record.Exception(() => MethodWithOutParameter(out var value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters()
{
// Arrange
var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter<TVal>(string a, out TVal value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithRefParameter(ref value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter<TVal>(ref TVal value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters()
{
// Arrange
var value = 0;
var exception = Record.Exception(() => MethodWithNullableParameter(value));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable<int> value)", methods[0]);
}
[Fact]
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes()
{
// Arrange
var exception = Record.Exception(() => new GenericClass<int>().Throw(0));
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.Throw(T parameter)", methods[0]);
}
[Fact]
public void StackTraceHelper_ProducesReadableOutput()
{
// Arrange
var expectedCallStack = new List<string>()
{
"Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()",
"string.Join(string separator, IEnumerable<string> values)",
"Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.GenericMethod<V>(ref V value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync<TValue>(TValue value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)",
"Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()",
};
Exception exception = null;
try
{
Method("test");
}
catch (Exception ex)
{
exception = ex;
}
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray();
// Assert
Assert.Equal(expectedCallStack, methodNames);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]);
}
[Fact]
public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute()
{
// Arrange
var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw());
// Act
var stackFrames = StackTraceHelper.GetFrames(exception);
// Assert
var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray();
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]);
Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]);
}
[Fact]
public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies()
{
// Arrange
var action = (Action)Expression.Lambda(
Expression.Throw(
Expression.New(typeof(Exception)))).Compile();
var exception = Record.Exception(action);
// Act
var frames = StackTraceHelper.GetFrames(exception).ToArray();
// Assert
var frame = frames[0];
Assert.Null(frame.FilePath);
Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString());
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
async Task<string> MethodAsync(int value)
{
await Task.Delay(0);
return GenericClass<byte>.GenericMethod(ref value);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
async Task<string> MethodAsync<TValue>(TValue value)
{
return await MethodAsync(1);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
string Method(string value)
{
return MethodAsync(value).GetAwaiter().GetResult();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static IEnumerable<string> Iterator()
{
yield return "Success";
throw new Exception();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithOutParameter(out int value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithGenericOutParameter<TVal>(string a, out TVal value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithRefParameter(ref int value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithGenericRefParameter<TVal>(ref TVal value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void MethodWithNullableParameter(int? value) => throw new Exception();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic();
class GenericClass<T>
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static string GenericMethod<V>(ref V value)
{
var returnVal = "";
for (var i = 0; i < 10; i++)
{
returnVal += string.Join(", ", Iterator());
}
return returnVal;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw(T parameter) => throw new Exception();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private void GenericMethod<T>(T val) where T : class => throw new Exception();
private class StackTraceHiddenAttribute : Attribute
{
}
[StackTraceHidden]
private class TypeWithStackTraceHiddenAttribute
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw() => ThrowCore();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static void ThrowStatic() => ThrowCore();
}
private class TypeWithMethodWithStackTraceHiddenAttribute
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
[StackTraceHidden]
public void MethodWithStackTraceHiddenAttribute()
{
ThrowCore();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void Throw() => MethodWithStackTraceHiddenAttribute();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static void ThrowCore() => throw new Exception();
}
}

View File

@ -0,0 +1,263 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class TypeNameHelperTest
{
[Theory]
// Predefined Types
[InlineData(typeof(int), "int")]
[InlineData(typeof(List<int>), "System.Collections.Generic.List<int>")]
[InlineData(typeof(Dictionary<int, string>), "System.Collections.Generic.Dictionary<int, string>")]
[InlineData(typeof(Dictionary<int, List<string>>), "System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<string>>")]
[InlineData(typeof(List<List<string>>), "System.Collections.Generic.List<System.Collections.Generic.List<string>>")]
// Classes inside NonGeneric class
[InlineData(typeof(A),
"Microsoft.Extensions.Internal.TypeNameHelperTest+A")]
[InlineData(typeof(B<int>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+B<int>")]
[InlineData(typeof(C<int, string>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+C<int, string>")]
[InlineData(typeof(B<B<string>>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+B<Microsoft.Extensions.Internal.TypeNameHelperTest+B<string>>")]
[InlineData(typeof(C<int, B<string>>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+C<int, Microsoft.Extensions.Internal.TypeNameHelperTest+B<string>>")]
// Classes inside Generic class
[InlineData(typeof(Outer<int>.D),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+D")]
[InlineData(typeof(Outer<int>.E<int>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+E<int>")]
[InlineData(typeof(Outer<int>.F<int, string>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+F<int, string>")]
[InlineData(typeof(Level1<int>.Level2<bool>.Level3<int>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<int>+Level2<bool>+Level3<int>")]
[InlineData(typeof(Outer<int>.E<Outer<int>.E<string>>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+E<Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+E<string>>")]
[InlineData(typeof(Outer<int>.F<int, Outer<int>.E<string>>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+F<int, Microsoft.Extensions.Internal.TypeNameHelperTest+Outer<int>+E<string>>")]
[InlineData(typeof(OuterGeneric<int>.InnerNonGeneric.InnerGeneric<int, string>.InnerGenericLeafNode<bool>),
"Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<int>+InnerNonGeneric+InnerGeneric<int, string>+InnerGenericLeafNode<bool>")]
public void Can_pretty_print_CLR_full_name(Type type, string expected)
{
Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type));
}
[Fact]
public void DoesNotPrintNamespace_ForGenericTypes_IfNullOrEmpty()
{
// Arrange
var type = typeof(ClassInGlobalNamespace<int>);
// Act & Assert
Assert.Equal("ClassInGlobalNamespace<int>", TypeNameHelper.GetTypeDisplayName(type));
}
[Theory]
// Predefined Types
[InlineData(typeof(int), "int")]
[InlineData(typeof(List<int>), "List<int>")]
[InlineData(typeof(Dictionary<int, string>), "Dictionary<int, string>")]
[InlineData(typeof(Dictionary<int, List<string>>), "Dictionary<int, List<string>>")]
[InlineData(typeof(List<List<string>>), "List<List<string>>")]
// Classes inside NonGeneric class
[InlineData(typeof(A), "A")]
[InlineData(typeof(B<int>), "B<int>")]
[InlineData(typeof(C<int, string>), "C<int, string>")]
[InlineData(typeof(C<int, B<string>>), "C<int, B<string>>")]
[InlineData(typeof(B<B<string>>), "B<B<string>>")]
// Classes inside Generic class
[InlineData(typeof(Outer<int>.D), "D")]
[InlineData(typeof(Outer<int>.E<int>), "E<int>")]
[InlineData(typeof(Outer<int>.F<int, string>), "F<int, string>")]
[InlineData(typeof(Outer<int>.F<int, Outer<int>.E<string>>), "F<int, E<string>>")]
[InlineData(typeof(Outer<int>.E<Outer<int>.E<string>>), "E<E<string>>")]
[InlineData(typeof(OuterGeneric<int>.InnerNonGeneric.InnerGeneric<int, string>.InnerGenericLeafNode<bool>), "InnerGenericLeafNode<bool>")]
public void Can_pretty_print_CLR_name(Type type, string expected)
{
Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, false));
}
[Theory]
[InlineData(typeof(void), "void")]
[InlineData(typeof(bool), "bool")]
[InlineData(typeof(byte), "byte")]
[InlineData(typeof(char), "char")]
[InlineData(typeof(decimal), "decimal")]
[InlineData(typeof(double), "double")]
[InlineData(typeof(float), "float")]
[InlineData(typeof(int), "int")]
[InlineData(typeof(long), "long")]
[InlineData(typeof(object), "object")]
[InlineData(typeof(sbyte), "sbyte")]
[InlineData(typeof(short), "short")]
[InlineData(typeof(string), "string")]
[InlineData(typeof(uint), "uint")]
[InlineData(typeof(ulong), "ulong")]
[InlineData(typeof(ushort), "ushort")]
public void Returns_common_name_for_built_in_types(Type type, string expected)
{
Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type));
}
[Theory]
[InlineData(typeof(int[]), true, "int[]")]
[InlineData(typeof(string[][]), true, "string[][]")]
[InlineData(typeof(int[,]), true, "int[,]")]
[InlineData(typeof(bool[,,,]), true, "bool[,,,]")]
[InlineData(typeof(A[,][,,]), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+A[,][,,]")]
[InlineData(typeof(List<int[,][,,]>), true, "System.Collections.Generic.List<int[,][,,]>")]
[InlineData(typeof(List<int[,,][,]>[,][,,]), false, "List<int[,,][,]>[,][,,]")]
public void Can_pretty_print_array_name(Type type, bool fullName, string expected)
{
Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName));
}
public static TheoryData GetOpenGenericsTestData()
{
var openDictionaryType = typeof(Dictionary<,>);
var genArgsDictionary = openDictionaryType.GetGenericArguments();
genArgsDictionary[0] = typeof(B<>);
var closedDictionaryType = openDictionaryType.MakeGenericType(genArgsDictionary);
var openLevelType = typeof(Level1<>.Level2<>.Level3<>);
var genArgsLevel = openLevelType.GetGenericArguments();
genArgsLevel[1] = typeof(string);
var closedLevelType = openLevelType.MakeGenericType(genArgsLevel);
var openInnerType = typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>);
var genArgsInnerType = openInnerType.GetGenericArguments();
genArgsInnerType[3] = typeof(bool);
var closedInnerType = openInnerType.MakeGenericType(genArgsInnerType);
return new TheoryData<Type, bool, string>
{
{ typeof(List<>), false, "List<>" },
{ typeof(Dictionary<,>), false , "Dictionary<,>" },
{ typeof(List<>), true , "System.Collections.Generic.List<>" },
{ typeof(Dictionary<,>), true , "System.Collections.Generic.Dictionary<,>" },
{ typeof(Level1<>.Level2<>.Level3<>), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2<>+Level3<>" },
{
typeof(PartiallyClosedGeneric<>).BaseType,
true,
"Microsoft.Extensions.Internal.TypeNameHelperTest+C<, int>"
},
{
typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>),
true,
"Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode<>"
},
{
closedDictionaryType,
true,
"System.Collections.Generic.Dictionary<Microsoft.Extensions.Internal.TypeNameHelperTest+B<>,>"
},
{
closedLevelType,
true,
"Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2<string>+Level3<>"
},
{
closedInnerType,
true,
"Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode<bool>"
}
};
}
[Theory]
[MemberData(nameof(GetOpenGenericsTestData))]
public void Can_pretty_print_open_generics(Type type, bool fullName, string expected)
{
Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName));
}
public static TheoryData GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData =>
new TheoryData<Type, string>
{
{ typeof(B<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+B<T>" },
{ typeof(C<,>),"Microsoft.Extensions.Internal.TypeNameHelperTest+C<T1, T2>" },
{ typeof(PartiallyClosedGeneric<>).BaseType,"Microsoft.Extensions.Internal.TypeNameHelperTest+C<T, int>" },
{ typeof(Level1<>.Level2<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<T1>+Level2<T2>" },
};
[Theory]
[MemberData(nameof(GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData))]
public void GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected)
{
// Arrange & Act
var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true);
// Assert
Assert.Equal(expected, actual);
}
public static TheoryData GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData =>
new TheoryData<Type, string>
{
{ typeof(B<>),"B<T>" },
{ typeof(C<,>),"C<T1, T2>" },
{ typeof(PartiallyClosedGeneric<>).BaseType,"C<T, int>" },
{ typeof(Level1<>.Level2<>),"Level2<T2>" },
};
[Theory]
[MemberData(nameof(GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData))]
public void GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected)
{
// Arrange & Act
var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: false, includeGenericParameterNames: true);
// Assert
Assert.Equal(expected, actual);
}
private class A { }
private class B<T> { }
private class C<T1, T2> { }
private class PartiallyClosedGeneric<T> : C<T, int> { }
private class Outer<T>
{
public class D { }
public class E<T1> { }
public class F<T1, T2> { }
}
private class OuterGeneric<T1>
{
public class InnerNonGeneric
{
public class InnerGeneric<T2, T3>
{
public class InnerGenericLeafNode<T4> { }
public class InnerLeafNode { }
}
}
}
private class Level1<T1>
{
public class Level2<T2>
{
public class Level3<T3>
{
}
}
}
}
}
internal class ClassInGlobalNamespace<T>
{
}

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Extensions.Internal.Test
{
public class ValueStopwatchTest
{
[Fact]
public void IsActiveIsFalseForDefaultValueStopwatch()
{
Assert.False(default(ValueStopwatch).IsActive);
}
[Fact]
public void IsActiveIsTrueWhenValueStopwatchStartedWithStartNew()
{
Assert.True(ValueStopwatch.StartNew().IsActive);
}
[Fact]
public void GetElapsedTimeThrowsIfValueStopwatchIsDefaultValue()
{
var stopwatch = default(ValueStopwatch);
Assert.Throws<InvalidOperationException>(() => stopwatch.GetElapsedTime());
}
[Fact]
public async Task GetElapsedTimeReturnsTimeElapsedSinceStart()
{
var stopwatch = ValueStopwatch.StartNew();
await Task.Delay(200);
Assert.True(stopwatch.GetElapsedTime().TotalMilliseconds > 0);
}
}
}

View File

@ -0,0 +1,113 @@
// 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;
using Xunit;
namespace Microsoft.Extensions.Internal
{
public class WebEncodersTests
{
[Theory]
[InlineData("", 1, 0)]
[InlineData("", 0, 1)]
[InlineData("0123456789", 9, 2)]
[InlineData("0123456789", Int32.MaxValue, 2)]
[InlineData("0123456789", 9, -1)]
public void Base64UrlDecode_BadOffsets(string input, int offset, int count)
{
// Act & assert
Assert.ThrowsAny<ArgumentException>(() =>
{
var retVal = WebEncoders.Base64UrlDecode(input, offset, count);
});
}
[Theory]
[InlineData("x")]
[InlineData("(x)")]
public void Base64UrlDecode_MalformedInput(string input)
{
// Act & assert
Assert.Throws<FormatException>(() =>
{
var retVal = WebEncoders.Base64UrlDecode(input);
});
}
[Theory]
[InlineData("", "")]
[InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")]
[InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")]
[InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")]
public void Base64UrlEncode_And_Decode(string base64Input, string expectedBase64Url)
{
// Arrange
byte[] input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray();
// Act & assert - 1
string actualBase64Url = WebEncoders.Base64UrlEncode(input, 3, input.Length - 5); // also helps test offsets
Assert.Equal(expectedBase64Url, actualBase64Url);
// Act & assert - 2
// Verify that values round-trip
byte[] roundTripped = WebEncoders.Base64UrlDecode("xx" + actualBase64Url + "yyy", 2, actualBase64Url.Length); // also helps test offsets
string roundTrippedAsBase64 = Convert.ToBase64String(roundTripped);
Assert.Equal(roundTrippedAsBase64, base64Input);
}
[Theory]
[InlineData("", "")]
[InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")]
[InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")]
[InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")]
public void Base64UrlEncode_And_Decode_WithBufferOffsets(string base64Input, string expectedBase64Url)
{
// Arrange
var input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray();
var buffer = new char[30];
var output = new char[30];
for (var i = 0; i < buffer.Length; i++)
{
buffer[i] = '^';
output[i] = '^';
}
// Act 1
var numEncodedChars =
WebEncoders.Base64UrlEncode(input, offset: 3, output: output, outputOffset: 4, count: input.Length - 5);
// Assert 1
var encodedString = new string(output, startIndex: 4, length: numEncodedChars);
Assert.Equal(expectedBase64Url, encodedString);
// Act 2
var roundTripInput = new string(output);
var roundTripped =
WebEncoders.Base64UrlDecode(roundTripInput, offset: 4, buffer: buffer, bufferOffset: 5, count: numEncodedChars);
// Assert 2, verify that values round-trip
var roundTrippedAsBase64 = Convert.ToBase64String(roundTripped);
Assert.Equal(roundTrippedAsBase64, base64Input);
}
[Theory]
[InlineData(0, 1, 0)]
[InlineData(0, 0, 1)]
[InlineData(10, 9, 2)]
[InlineData(10, Int32.MaxValue, 2)]
[InlineData(10, 9, -1)]
public void Base64UrlEncode_BadOffsets(int inputLength, int offset, int count)
{
// Arrange
byte[] input = new byte[inputLength];
// Act & assert
Assert.ThrowsAny<ArgumentException>(() =>
{
var retVal = WebEncoders.Base64UrlEncode(input, offset, count);
});
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
namespace ThrowingLibrary
{
// Throwing an exception in the current assembly always seems to populate the full stack
// trace regardless of symbol type. This type exists to simulate an exception thrown
// across assemblies which is the typical use case for StackTraceHelper.
public static class Thrower
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Throw()
{
throw new DivideByZeroException();
}
}
}

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DebugType>portable</DebugType>
</PropertyGroup>
</Project>