Created
September 11, 2017 22:54
-
-
Save pakrym/cbe0c0be3742f4250660ea62614bb972 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Xunit; | |
using Xunit.Abstractions; | |
using Xunit.Sdk; | |
[assembly: TestFramework("Microsoft.AspNetCore.AzureAppServices.FunctionalTests.Framework.VeryParallelFramework", "Microsoft.AspNetCore.AzureAppServices.FunctionalTests")] | |
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests.Framework | |
{ | |
class VeryParallelFramework: XunitTestFramework | |
{ | |
public VeryParallelFramework(IMessageSink messageSink) : base(messageSink) | |
{ | |
} | |
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) | |
{ | |
return new VeryParallelTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); | |
} | |
} | |
class VeryParallelTestFrameworkExecutor: XunitTestFrameworkExecutor | |
{ | |
public VeryParallelTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) | |
{ | |
} | |
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) | |
{ | |
using (var assemblyRunner = new VeryParallelTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions)) | |
await assemblyRunner.RunAsync(); | |
} | |
} | |
class VeryParallelTestAssemblyRunner : XunitTestAssemblyRunner | |
{ | |
public VeryParallelTestAssemblyRunner(ITestAssembly testAssembly, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions) | |
{ | |
} | |
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, | |
CancellationTokenSource cancellationTokenSource) | |
{ | |
return new VeryParallelTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync(); | |
} | |
} | |
class VeryParallelTestCollectionRunner : XunitTestCollectionRunner | |
{ | |
private readonly IMessageSink _diagnosticMessageSink; | |
public VeryParallelTestCollectionRunner(ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) | |
{ | |
_diagnosticMessageSink = diagnosticMessageSink; | |
} | |
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases) | |
{ | |
return new VeryParallelTestClassRunner(testClass, @class, testCases, _diagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings).RunAsync(); | |
} | |
} | |
class VeryParallelTestClassRunner : XunitTestClassRunner | |
{ | |
public VeryParallelTestClassRunner(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, IDictionary<Type, object> collectionFixtureMappings) : base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings) | |
{ | |
} | |
protected override async Task<RunSummary> RunTestMethodsAsync() | |
{ | |
var summary = new RunSummary(); | |
IEnumerable<IXunitTestCase> orderedTestCases; | |
try | |
{ | |
orderedTestCases = TestCaseOrderer.OrderTestCases(TestCases); | |
} | |
catch (Exception ex) | |
{ | |
var innerEx = ex.Unwrap(); | |
DiagnosticMessageSink.OnMessage(new DiagnosticMessage($"Test case orderer '{TestCaseOrderer.GetType().FullName}' threw '{innerEx.GetType().FullName}' during ordering: {innerEx.Message}{Environment.NewLine}{innerEx.StackTrace}")); | |
orderedTestCases = TestCases.ToList(); | |
} | |
var constructorArguments = CreateTestClassConstructorArguments(); | |
foreach (var task in orderedTestCases.GroupBy(tc => tc.TestMethod, TestMethodComparer.Instance) | |
.Select(method => Task.Run(() => RunTestMethodAsync(method.Key, (IReflectionMethodInfo)method.Key.Method, method, constructorArguments))).ToArray()) | |
{ | |
var result = await task; | |
summary.Aggregate(result); | |
if (CancellationTokenSource.IsCancellationRequested) | |
break; | |
} | |
return summary; | |
} | |
protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, object[] constructorArguments) | |
{ | |
return new VeryParallelTestMethodRunner(testMethod, Class, method, testCases, DiagnosticMessageSink, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, constructorArguments).RunAsync(); | |
} | |
} | |
class VeryParallelTestMethodRunner : XunitTestMethodRunner | |
{ | |
public VeryParallelTestMethodRunner(ITestMethod testMethod, IReflectionTypeInfo @class, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, object[] constructorArguments) : base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments) | |
{ | |
} | |
protected override async Task<RunSummary> RunTestCasesAsync() | |
{ | |
var summary = new RunSummary(); | |
foreach (var task in TestCases.Select(testCase => Task.Run(() => RunTestCaseAsync(testCase))).ToArray()) | |
{ | |
summary.Aggregate(await task); | |
if (CancellationTokenSource.IsCancellationRequested) | |
break; | |
} | |
return summary; | |
} | |
} | |
[XunitTestCaseDiscoverer("Microsoft.AspNetCore.AzureAppServices.FunctionalTests.Framework.VeryParallelTheoryDiscoverer", "Microsoft.AspNetCore.AzureAppServices.FunctionalTests")] | |
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | |
public class VeryParallelTheoryAttribute : FactAttribute { } | |
public class VeryParallelTheoryDiscoverer : TheoryDiscoverer | |
{ | |
public VeryParallelTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) | |
{ | |
} | |
#pragma warning disable 672 | |
protected override IXunitTestCase CreateTestCaseForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) | |
#pragma warning restore 672 | |
{ | |
return new VeryParallelTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow); | |
} | |
#pragma warning disable 672 | |
protected override IXunitTestCase CreateTestCaseForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) | |
#pragma warning restore 672 | |
{ | |
return new VeryParallelTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);; | |
} | |
} | |
class VeryParallelTestCase : XunitTestCase | |
{ | |
#pragma warning disable CS0618 // Type or member is obsolete | |
public VeryParallelTestCase():base() | |
#pragma warning restore CS0618 // Type or member is obsolete | |
{ | |
} | |
public VeryParallelTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) | |
{ | |
} | |
public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, | |
CancellationTokenSource cancellationTokenSource) | |
{ | |
return new VeryParallelTheoryTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource).RunAsync(); | |
} | |
} | |
class VeryParallelTheoryTestCase : XunitTheoryTestCase | |
{ | |
#pragma warning disable CS0618 // Type or member is obsolete | |
public VeryParallelTheoryTestCase():base() | |
#pragma warning restore CS0618 // Type or member is obsolete | |
{ | |
} | |
public VeryParallelTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod) : base(diagnosticMessageSink, defaultMethodDisplay, testMethod) | |
{ | |
} | |
public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, | |
CancellationTokenSource cancellationTokenSource) | |
{ | |
return new VeryParallelTheoryTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource).RunAsync(); | |
} | |
} | |
internal class VeryParallelTheoryTestCaseRunner: XunitTheoryTestCaseRunner | |
{ | |
static readonly object[] NoArguments = new object[0]; | |
readonly ExceptionAggregator cleanupAggregator = new ExceptionAggregator(); | |
Exception dataDiscoveryException; | |
readonly IMessageSink diagnosticMessageSink; | |
readonly List<XunitTestRunner> testRunners = new List<XunitTestRunner>(); | |
readonly List<IDisposable> toDispose = new List<IDisposable>(); | |
/// <summary> | |
/// Initializes a new instance of the <see cref="XunitTheoryTestCaseRunner"/> class. | |
/// </summary> | |
/// <param name="testCase">The test case to be run.</param> | |
/// <param name="displayName">The display name of the test case.</param> | |
/// <param name="skipReason">The skip reason, if the test is to be skipped.</param> | |
/// <param name="constructorArguments">The arguments to be passed to the test class constructor.</param> | |
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param> | |
/// <param name="messageBus">The message bus to report run status to.</param> | |
/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param> | |
/// <param name="cancellationTokenSource">The task cancellation token source, used to cancel the test run.</param> | |
public VeryParallelTheoryTestCaseRunner(IXunitTestCase testCase, | |
string displayName, | |
string skipReason, | |
object[] constructorArguments, | |
IMessageSink diagnosticMessageSink, | |
IMessageBus messageBus, | |
ExceptionAggregator aggregator, | |
CancellationTokenSource cancellationTokenSource) | |
: base(testCase, displayName, skipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource) | |
{ | |
this.diagnosticMessageSink = diagnosticMessageSink; | |
} | |
/// <inheritdoc/> | |
protected override async Task AfterTestCaseStartingAsync() | |
{ | |
await base.AfterTestCaseStartingAsync(); | |
try | |
{ | |
var dataAttributes = TestCase.TestMethod.Method.GetCustomAttributes(typeof(DataAttribute)); | |
foreach (var dataAttribute in dataAttributes) | |
{ | |
var discovererAttribute = dataAttribute.GetCustomAttributes(typeof(DataDiscovererAttribute)).First(); | |
var args = discovererAttribute.GetConstructorArguments().Cast<string>().ToList(); | |
var discovererType = SerializationHelper.GetType(args[1], args[0]); | |
if (discovererType == null) | |
{ | |
var reflectionAttribute = dataAttribute as IReflectionAttributeInfo; | |
if (reflectionAttribute != null) | |
Aggregator.Add(new InvalidOperationException($"Data discoverer specified for {reflectionAttribute.Attribute.GetType()} on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not exist.")); | |
else | |
Aggregator.Add(new InvalidOperationException($"A data discoverer specified on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not exist.")); | |
continue; | |
} | |
IDataDiscoverer discoverer; | |
try | |
{ | |
discoverer = ExtensibilityPointFactory.GetDataDiscoverer(diagnosticMessageSink, discovererType); | |
} | |
catch (InvalidCastException) | |
{ | |
var reflectionAttribute = dataAttribute as IReflectionAttributeInfo; | |
if (reflectionAttribute != null) | |
Aggregator.Add(new InvalidOperationException($"Data discoverer specified for {reflectionAttribute.Attribute.GetType()} on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not implement IDataDiscoverer.")); | |
else | |
Aggregator.Add(new InvalidOperationException($"A data discoverer specified on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not implement IDataDiscoverer.")); | |
continue; | |
} | |
var data = discoverer.GetData(dataAttribute, TestCase.TestMethod.Method); | |
if (data == null) | |
{ | |
Aggregator.Add(new InvalidOperationException($"Test data returned null for {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name}. Make sure it is statically initialized before this test method is called.")); | |
continue; | |
} | |
foreach (var dataRow in data) | |
{ | |
toDispose.AddRange(dataRow.OfType<IDisposable>()); | |
ITypeInfo[] resolvedTypes = null; | |
var methodToRun = TestMethod; | |
var convertedDataRow = methodToRun.ResolveMethodArguments(dataRow); | |
if (methodToRun.IsGenericMethodDefinition) | |
{ | |
resolvedTypes = TestCase.TestMethod.Method.ResolveGenericTypes(convertedDataRow); | |
methodToRun = methodToRun.MakeGenericMethod(resolvedTypes.Select(t => ((IReflectionTypeInfo)t).Type).ToArray()); | |
} | |
var parameterTypes = methodToRun.GetParameters().Select(p => p.ParameterType).ToArray(); | |
convertedDataRow = Reflector.ConvertArguments(convertedDataRow, parameterTypes); | |
var theoryDisplayName = TestCase.TestMethod.Method.GetDisplayNameWithArguments(DisplayName, convertedDataRow, resolvedTypes); | |
var test = new XunitTest(TestCase, theoryDisplayName); | |
var skipReason = SkipReason ?? dataAttribute.GetNamedArgument<string>("Skip"); | |
testRunners.Add(new VeryParallelRunner(test, MessageBus, TestClass, ConstructorArguments, methodToRun, convertedDataRow, skipReason, BeforeAfterAttributes, Aggregator, CancellationTokenSource)); | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
// Stash the exception so we can surface it during RunTestAsync | |
dataDiscoveryException = ex; | |
} | |
} | |
/// <inheritdoc/> | |
protected override Task BeforeTestCaseFinishedAsync() | |
{ | |
Aggregator.Aggregate(cleanupAggregator); | |
return base.BeforeTestCaseFinishedAsync(); | |
} | |
/// <inheritdoc/> | |
protected override async Task<RunSummary> RunTestAsync() | |
{ | |
if (dataDiscoveryException != null) | |
return RunTest_DataDiscoveryException(); | |
var runSummary = new RunSummary(); | |
foreach (var testRunner in testRunners) | |
runSummary.Aggregate(await testRunner.RunAsync()); | |
// Run the cleanup here so we can include cleanup time in the run summary, | |
// but save any exceptions so we can surface them during the cleanup phase, | |
// so they get properly reported as test case cleanup failures. | |
var timer = new ExecutionTimer(); | |
foreach (var disposable in toDispose) | |
timer.Aggregate(() => cleanupAggregator.Run(disposable.Dispose)); | |
runSummary.Time += timer.Total; | |
return runSummary; | |
} | |
RunSummary RunTest_DataDiscoveryException() | |
{ | |
var test = new XunitTest(TestCase, DisplayName); | |
if (!MessageBus.QueueMessage(new TestStarting(test))) | |
CancellationTokenSource.Cancel(); | |
else if (!MessageBus.QueueMessage(new TestFailed(test, 0, null, dataDiscoveryException.Unwrap()))) | |
CancellationTokenSource.Cancel(); | |
if (!MessageBus.QueueMessage(new TestFinished(test, 0, null))) | |
CancellationTokenSource.Cancel(); | |
return new RunSummary { Total = 1, Failed = 1 }; | |
} | |
} | |
internal class VeryParallelRunner : XunitTestRunner | |
{ | |
public VeryParallelRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) | |
: base(test, messageBus, testClass, FixOutpuHelper(constructorArguments), testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource) | |
{ | |
} | |
private static object[] FixOutpuHelper(object[] constructorArguments) | |
{ | |
var newArguments = new object[constructorArguments.Length]; | |
Array.Copy(constructorArguments, newArguments, constructorArguments.Length); | |
for (int i = 0; i < newArguments.Length; i++) | |
{ | |
var constructorArgument = constructorArguments[i]; | |
if (constructorArgument is ITestOutputHelper) | |
{ | |
constructorArguments[i] = new TestOutputHelper(); | |
} | |
} | |
return newArguments; | |
} | |
protected override async Task<Tuple<decimal, string>> InvokeTestAsync(ExceptionAggregator aggregator) | |
{ | |
var output = string.Empty; | |
TestOutputHelper testOutputHelper = null; | |
foreach (object obj in ConstructorArguments) | |
{ | |
testOutputHelper = obj as TestOutputHelper; | |
if (testOutputHelper != null) | |
break; | |
} | |
if (testOutputHelper != null) | |
testOutputHelper.Initialize(MessageBus, Test); | |
var executionTime = await InvokeTestMethodAsync(aggregator); | |
if (testOutputHelper != null) | |
{ | |
output = testOutputHelper.Output; | |
testOutputHelper.Uninitialize(); | |
} | |
return Tuple.Create(executionTime, output); | |
} | |
} | |
static class SerializationHelper | |
{ | |
/// <summary> | |
/// Converts an assembly qualified type name into a <see cref="Type"/> object. | |
/// </summary> | |
/// <param name="assemblyQualifiedTypeName">The assembly qualified type name.</param> | |
/// <returns>The instance of the <see cref="Type"/>, if available; <c>null</c>, otherwise.</returns> | |
public static Type GetType(string assemblyQualifiedTypeName) | |
{ | |
var firstOpenSquare = assemblyQualifiedTypeName.IndexOf('['); | |
if (firstOpenSquare > 0) | |
{ | |
var backtick = assemblyQualifiedTypeName.IndexOf('`'); | |
if (backtick > 0 && backtick < firstOpenSquare) | |
{ | |
// Run the string looking for the matching closing square brace. Can't just assume the last one | |
// is the end, since the type could be trailed by array designators. | |
var depth = 1; | |
var lastOpenSquare = firstOpenSquare + 1; | |
var sawNonArrayDesignator = false; | |
for (; depth > 0 && lastOpenSquare < assemblyQualifiedTypeName.Length; ++lastOpenSquare) | |
{ | |
switch (assemblyQualifiedTypeName[lastOpenSquare]) | |
{ | |
case '[': | |
++depth; | |
break; | |
case ']': | |
--depth; | |
break; | |
case ',': break; | |
default: | |
sawNonArrayDesignator = true; | |
break; | |
} | |
} | |
if (sawNonArrayDesignator) | |
{ | |
if (depth != 0) // Malformed, because we never closed what we opened | |
return null; | |
var genericArgument = assemblyQualifiedTypeName.Substring( | |
firstOpenSquare + 1, lastOpenSquare - firstOpenSquare - 2); // Strip surrounding [ and ] | |
var innerTypeNames = | |
SplitAtOuterCommas(genericArgument) | |
.Select(x => x.Substring(1, x.Length - 2)); // Strip surrounding [ and ] from each type name | |
var innerTypes = innerTypeNames.Select(s => GetType(s)).ToArray(); | |
if (innerTypes.Any(t => t == null)) | |
return null; | |
var genericDefinitionName = assemblyQualifiedTypeName.Substring(0, firstOpenSquare) + | |
assemblyQualifiedTypeName.Substring(lastOpenSquare); | |
var genericDefinition = GetType(genericDefinitionName); | |
if (genericDefinition == null) | |
return null; | |
// Push array ranks so we can get down to the actual generic definition | |
var arrayRanks = new Stack<int>(); | |
while (genericDefinition.IsArray) | |
{ | |
arrayRanks.Push(genericDefinition.GetArrayRank()); | |
genericDefinition = genericDefinition.GetElementType(); | |
} | |
var closedGenericType = genericDefinition.MakeGenericType(innerTypes); | |
while (arrayRanks.Count > 0) | |
{ | |
var rank = arrayRanks.Pop(); | |
closedGenericType = rank > 1 ? closedGenericType.MakeArrayType(rank) : closedGenericType.MakeArrayType(); | |
} | |
return closedGenericType; | |
} | |
} | |
} | |
IList<string> parts = SplitAtOuterCommas(assemblyQualifiedTypeName, true); | |
return | |
parts.Count == 0 ? null : parts.Count == 1 ? Type.GetType(parts[0]) : GetType(parts[1], parts[0]); | |
} | |
/// <summary> | |
/// Converts an assembly name + type name into a <see cref="Type"/> object. | |
/// </summary> | |
/// <param name="assemblyName">The assembly name.</param> | |
/// <param name="typeName">The type name.</param> | |
/// <returns>The instance of the <see cref="Type"/>, if available; <c>null</c>, otherwise.</returns> | |
public static Type GetType(string assemblyName, string typeName) | |
{ | |
#if XUNIT_FRAMEWORK // This behavior is only for v2, and only done on the remote app domain side | |
if (assemblyName.EndsWith(ExecutionHelper.SubstitutionToken, StringComparison.OrdinalIgnoreCase)) | |
assemblyName = | |
assemblyName.Substring(0, assemblyName.Length - ExecutionHelper.SubstitutionToken.Length + 1) + ExecutionHelper.PlatformSuffix; | |
#endif | |
#if PLATFORM_DOTNET | |
Assembly assembly = null; | |
try | |
{ | |
// Make sure we only use the short form | |
var an = new AssemblyName(assemblyName); | |
assembly = Assembly.Load(new AssemblyName { Name = an.Name, Version = an.Version }); | |
} | |
catch { } | |
#else | |
// Support both long name ("assembly, version=x.x.x.x, etc.") and short name ("assembly") | |
var assembly = AppDomain.CurrentDomain.GetAssemblies() | |
.FirstOrDefault(a => a.FullName == assemblyName || a.GetName().Name == assemblyName); | |
if (assembly == null) | |
{ | |
try | |
{ | |
assembly = Assembly.Load(assemblyName); | |
} | |
catch | |
{ | |
} | |
} | |
#endif | |
if (assembly == null) | |
return null; | |
return assembly.GetType(typeName); | |
} | |
/// <summary> | |
/// Retrieves a substring from the string, with whitespace trimmed on both ends. | |
/// </summary> | |
/// <param name="str">The string.</param> | |
/// <param name="startIndex">The starting index.</param> | |
/// <param name="length">The length.</param> | |
/// <returns> | |
/// A substring starting no earlier than startIndex and ending no later | |
/// than startIndex + length. | |
/// </returns> | |
static string SubstringTrim(string str, int startIndex, int length) | |
{ | |
int endIndex = startIndex + length; | |
while (startIndex < endIndex && char.IsWhiteSpace(str[startIndex])) | |
startIndex++; | |
while (endIndex > startIndex && char.IsWhiteSpace(str[endIndex - 1])) | |
endIndex--; | |
return str.Substring(startIndex, endIndex - startIndex); | |
} | |
static IList<string> SplitAtOuterCommas(string value, bool trimWhitespace = false) | |
{ | |
var results = new List<string>(); | |
var startIndex = 0; | |
var endIndex = 0; | |
var depth = 0; | |
for (; endIndex < value.Length; ++endIndex) | |
{ | |
switch (value[endIndex]) | |
{ | |
case '[': | |
++depth; | |
break; | |
case ']': | |
--depth; | |
break; | |
case ',': | |
if (depth == 0) | |
{ | |
results.Add( | |
trimWhitespace | |
? SubstringTrim(value, startIndex, endIndex - startIndex) | |
: value.Substring(startIndex, endIndex - startIndex)); | |
startIndex = endIndex + 1; | |
} | |
break; | |
} | |
} | |
if (depth != 0 || startIndex >= endIndex) | |
{ | |
results.Clear(); | |
} | |
else | |
{ | |
results.Add( | |
trimWhitespace ? SubstringTrim(value, startIndex, endIndex - startIndex) : value.Substring(startIndex, endIndex - startIndex)); | |
} | |
return results; | |
} | |
} | |
internal static class ExceptionExtensions | |
{ | |
/// <summary> | |
/// Unwraps an exception to remove any wrappers, like <see cref="TargetInvocationException"/>. | |
/// </summary> | |
/// <param name="ex">The exception to unwrap.</param> | |
/// <returns>The unwrapped exception.</returns> | |
public static Exception Unwrap(this Exception ex) | |
{ | |
while (true) | |
{ | |
var tiex = ex as TargetInvocationException; | |
if (tiex == null) | |
return ex; | |
ex = tiex.InnerException; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment