diff --git a/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs b/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs index d62f11fe2..489ad4d72 100644 --- a/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs +++ b/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs @@ -23,48 +23,41 @@ namespace Microsoft.Python.Analysis.Analyzer { [DebuggerDisplay("{Name} : {FilePath}")] internal readonly struct AnalysisModuleKey : IEquatable { - private enum KeyType { Default, Typeshed, LibraryAsDocument } - - private readonly KeyType _type; public string Name { get; } public string FilePath { get; } - public bool IsTypeshed => _type == KeyType.Typeshed; - public bool IsLibraryAsDocument => _type == KeyType.LibraryAsDocument; + public bool IsTypeshed { get; } + public bool IsNonUserAsDocument { get; } public AnalysisModuleKey(IPythonModule module) { Name = module.Name; FilePath = module.ModuleType == ModuleType.CompiledBuiltin ? null : module.FilePath; - _type = module is StubPythonModule stub && stub.IsTypeshed - ? KeyType.Typeshed - : module.ModuleType == ModuleType.Library && module is IDocument document && document.IsOpen - ? KeyType.LibraryAsDocument - : KeyType.Default; + IsTypeshed = module is StubPythonModule stub && stub.IsTypeshed; + IsNonUserAsDocument = (module.IsNonUserFile() || module.IsCompiled()) && module is IDocument document && document.IsOpen; } - public AnalysisModuleKey(string name, string filePath, bool isTypeshed) { - Name = name; - FilePath = filePath; - _type = isTypeshed ? KeyType.Typeshed : KeyType.Default; - } + public AnalysisModuleKey(string name, string filePath, bool isTypeshed) + : this(name, filePath, isTypeshed, false) { } - private AnalysisModuleKey(string name, string filePath, KeyType type) { + private AnalysisModuleKey(string name, string filePath, bool isTypeshed, bool isNonUserAsDocument) { Name = name; FilePath = filePath; - _type = type; + IsTypeshed = isTypeshed; + IsNonUserAsDocument = isNonUserAsDocument; } - public AnalysisModuleKey GetLibraryAsDocumentKey() => new AnalysisModuleKey(Name, FilePath, KeyType.LibraryAsDocument); + public AnalysisModuleKey GetNonUserAsDocumentKey() => new AnalysisModuleKey(Name, FilePath, IsTypeshed, true); public bool Equals(AnalysisModuleKey other) - => Name.EqualsOrdinal(other.Name) && FilePath.PathEquals(other.FilePath) && _type == other._type; + => Name.EqualsOrdinal(other.Name) && FilePath.PathEquals(other.FilePath) && IsTypeshed == other.IsTypeshed && IsNonUserAsDocument == other.IsNonUserAsDocument; public override bool Equals(object obj) => obj is AnalysisModuleKey other && Equals(other); public override int GetHashCode() { unchecked { - var hashCode = (Name != null ? Name.GetHashCode() : 0); + var hashCode = Name != null ? Name.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (FilePath != null ? FilePath.GetPathHashCode() : 0); - hashCode = (hashCode * 397) ^ _type.GetHashCode(); + hashCode = (hashCode * 397) ^ IsTypeshed.GetHashCode(); + hashCode = (hashCode * 397) ^ IsNonUserAsDocument.GetHashCode(); return hashCode; } } diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs index 3ce37b951..8037a5868 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Python.Analysis.Caching; @@ -164,9 +165,9 @@ public void EnqueueDocumentForAnalysis(IPythonModule module, PythonAst ast, int // It is possible that parsing request for the library has been started when document is open, // but it is closed at the moment of analysis and then become open again. // In this case, we still need to analyze the document, but using correct entry. - var libraryAsDocumentKey = key.GetLibraryAsDocumentKey(); - if (entry.PreviousAnalysis is LibraryAnalysis && _analysisEntries.TryGetValue(libraryAsDocumentKey, out var documentEntry)) { - key = libraryAsDocumentKey; + var nonUserAsDocumentKey = key.GetNonUserAsDocumentKey(); + if (entry.PreviousAnalysis is LibraryAnalysis && _analysisEntries.TryGetValue(nonUserAsDocumentKey, out var documentEntry)) { + key = nonUserAsDocumentKey; entry = documentEntry; } @@ -237,7 +238,7 @@ private void AnalyzeDocument(in AnalysisModuleKey key, in PythonAnalyzerEntry en ActivityTracker.StartTracking(); _log?.Log(TraceEventType.Verbose, $"Analysis of {entry.Module.Name}({entry.Module.ModuleType}) queued"); - var graphVersion = _dependencyResolver.ChangeValue(key, entry, entry.IsUserOrBuiltin || key.IsLibraryAsDocument, dependencies); + var graphVersion = _dependencyResolver.ChangeValue(key, entry, entry.IsUserOrBuiltin || key.IsNonUserAsDocument, dependencies); lock (_syncObj) { if (_version > graphVersion) { @@ -299,6 +300,11 @@ private bool TryCreateSession(in int graphVersion, in PythonAnalyzerEntry entry, } private void StartNextSession(Task task) { + if (task.IsFaulted && task.Exception != null) { + var exception = task.Exception.InnerException; + ExceptionDispatchInfo.Capture(exception).Throw(); + } + PythonAnalyzerSession session; lock (_syncObj) { if (_nextSession == null) { diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs index c088825eb..19f7faeb4 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs @@ -113,7 +113,7 @@ public bool IsValidVersion(int version, out IPythonModule module, out PythonAst } if (ast == null) { - Debug.Assert(!(_previousAnalysis is LibraryAnalysis), $"Library module {module.Name} of type {module.ModuleType} has been analyzed already!"); + Debug.Assert(!(_analysisVersion <= version && _previousAnalysis is LibraryAnalysis), $"Library module {module.Name} of type {module.ModuleType} has been analyzed already!"); return false; } diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs index 6bfcac9df..66441ccd0 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs @@ -17,6 +17,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Python.Analysis.Analyzer.Evaluation; @@ -28,6 +29,7 @@ using Microsoft.Python.Core; using Microsoft.Python.Core.Logging; using Microsoft.Python.Core.Services; +using Microsoft.Python.Core.Testing; using Microsoft.Python.Parsing.Ast; namespace Microsoft.Python.Analysis.Analyzer { @@ -207,6 +209,7 @@ private async Task AnalyzeAffectedEntriesAsync(Stopwatch stopWatch) { if (Interlocked.Increment(ref _runningTasks) >= _maxTaskRunning || _walker.Remaining == 1) { RunAnalysis(node, stopWatch); } else { + ace.AddOne(); StartAnalysis(node, ace, stopWatch).DoNotWait(); } } @@ -247,7 +250,6 @@ private Task StartAnalysis(IDependencyChainNode node, Async /// private void Analyze(IDependencyChainNode node, AsyncCountdownEvent ace, Stopwatch stopWatch) { try { - ace?.AddOne(); var entry = node.Value; if (!entry.IsValidVersion(_walker.Version, out var module, out var ast)) { @@ -370,6 +372,10 @@ private void LogException(IPythonModule module, Exception exception) { if (_log != null) { _log.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) failed. {exception}"); } + + if (TestEnvironment.Current != null) { + ExceptionDispatchInfo.Capture(exception).Throw(); + } } private IDocumentAnalysis CreateAnalysis(IDependencyChainNode node, IDocument document, PythonAst ast, int version, ModuleWalker walker, bool isCanceled) { diff --git a/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs b/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs index 6cf42b965..bc9017442 100644 --- a/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs @@ -1,4 +1,19 @@ -using Microsoft.Python.Analysis.Types; +// Copyright(c) Microsoft Corporation +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the License); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using Microsoft.Python.Analysis.Types; namespace Microsoft.Python.Analysis { public static class ClassMemberExtensions { diff --git a/src/Analysis/Ast/Impl/Extensions/PythonModuleExtensions.cs b/src/Analysis/Ast/Impl/Extensions/PythonModuleExtensions.cs index 7effc7b6f..ca03c97d8 100644 --- a/src/Analysis/Ast/Impl/Extensions/PythonModuleExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/PythonModuleExtensions.cs @@ -67,5 +67,8 @@ internal static string GetComment(this IPythonModule module, int lineNum) { return line.Substring(commentPos + 1).Trim('\t', ' '); } + + internal static bool IsNonUserFile(this IPythonModule module) => module.ModuleType.IsNonUserFile(); + internal static bool IsCompiled(this IPythonModule module) => module.ModuleType.IsCompiled(); } } diff --git a/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs b/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs index 992fb79ea..1075dc642 100644 --- a/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs @@ -27,15 +27,5 @@ public static bool IsGenericParameter(this IPythonType value) public static bool IsGeneric(this IPythonType value) => value is IGenericTypeParameter || (value is IGenericType gt && gt.IsGeneric); - - public static void TransferDocumentationAndLocation(this IPythonType s, IPythonType d) { - if (s != d && s is PythonType src && d is PythonType dst) { - var documentation = src.Documentation; - if (!string.IsNullOrEmpty(documentation)) { - dst.SetDocumentation(documentation); - } - dst.Location = src.Location; - } - } } } diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/ModuleType.cs b/src/Analysis/Ast/Impl/Modules/Definitions/ModuleType.cs index 64ab06683..a18205271 100644 --- a/src/Analysis/Ast/Impl/Modules/Definitions/ModuleType.cs +++ b/src/Analysis/Ast/Impl/Modules/Definitions/ModuleType.cs @@ -60,4 +60,9 @@ public enum ModuleType { /// Specialized } + + public static class ModuleTypeExtensions { + public static bool IsNonUserFile(this ModuleType type) => type == ModuleType.Library || type == ModuleType.Stub; + public static bool IsCompiled(this ModuleType type) => type == ModuleType.Compiled || type == ModuleType.CompiledBuiltin; + } } diff --git a/src/Core/Impl/Extensions/TaskExtensions.cs b/src/Core/Impl/Extensions/TaskExtensions.cs index d824dfed6..76643bb4f 100644 --- a/src/Core/Impl/Extensions/TaskExtensions.cs +++ b/src/Core/Impl/Extensions/TaskExtensions.cs @@ -17,7 +17,7 @@ using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.Python.Core.Threading; +using Microsoft.Python.Core.Testing; namespace Microsoft.Python.Core { public static class TaskExtensions { @@ -52,6 +52,12 @@ private static void SetCompletionResultToContinuation(Task task, object st /// is always ignored. /// public static void DoNotWait(this Task task) { + if (TestEnvironment.Current != null && TestEnvironment.Current.TryAddTaskToWait(task)) { + if (!task.IsCompleted) { + return; + } + } + if (task.IsCompleted) { ReThrowTaskException(task); return; diff --git a/src/Core/Test/TestLogger.cs b/src/Core/Test/TestLogger.cs index 4f633e2d2..8270c5f15 100644 --- a/src/Core/Test/TestLogger.cs +++ b/src/Core/Test/TestLogger.cs @@ -37,7 +37,6 @@ public void Dispose() { public TraceEventType LogLevel { get; set; } = TraceEventType.Verbose; public void Log(TraceEventType eventType, IFormattable message) => Log(eventType, message.ToString()); public void Log(TraceEventType eventType, string message) { - var m = $"[{TestEnvironmentImpl.Elapsed()}]: {message}"; switch (eventType) { case TraceEventType.Error: diff --git a/src/PLS.sln b/src/PLS.sln index d19e7a2d0..ab969a558 100644 --- a/src/PLS.sln +++ b/src/PLS.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27929.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29003.217 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Python.LanguageServer", "LanguageServer\Impl\Microsoft.Python.LanguageServer.csproj", "{4E648207-0C9F-45DB-AA30-84BCDAFE698D}" EndProject diff --git a/src/UnitTests/Core/Impl/Ben.Demystifier/ILReader.cs b/src/UnitTests/Core/Impl/Ben.Demystifier/ILReader.cs deleted file mode 100644 index 2a75c8e32..000000000 --- a/src/UnitTests/Core/Impl/Ben.Demystifier/ILReader.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Ben A Adams. All rights reserved. -// Licensed under the Apache License, Version 2.0. -// -// Python Tools for Visual Studio -// Copyright(c) Microsoft Corporation -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the License); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -// MERCHANTABILITY OR NON-INFRINGEMENT. -// -// See the Apache Version 2.0 License for specific language governing -// permissions and limitations under the License. - -using System; -using System.Reflection; -using System.Reflection.Emit; - -namespace TestUtilities.Ben.Demystifier { - internal sealed class ILReader { - private static readonly OpCode[] singleByteOpCode; - private static readonly OpCode[] doubleByteOpCode; - - private readonly byte[] _cil; - private int _ptr; - - public ILReader(byte[] cil) => _cil = cil; - - public OpCode OpCode { get; private set; } - public int MetadataToken { get; private set; } - public MemberInfo Operand { get; private set; } - - public bool Read(MethodBase methodInfo) { - if (_ptr < _cil.Length) { - OpCode = ReadOpCode(); - Operand = ReadOperand(OpCode, methodInfo); - return true; - } - return false; - } - - private OpCode ReadOpCode() { - var instruction = ReadByte(); - return instruction < 254 ? singleByteOpCode[instruction] : doubleByteOpCode[ReadByte()]; - } - - private MemberInfo ReadOperand(OpCode code, MethodBase methodInfo) { - MetadataToken = 0; - switch (code.OperandType) { - case OperandType.InlineMethod: - MetadataToken = ReadInt(); - Type[] methodArgs = null; - if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo))) { - methodArgs = methodInfo.GetGenericArguments(); - } - - Type[] typeArgs = null; - if (methodInfo.DeclaringType != null) { - typeArgs = methodInfo.DeclaringType.GetGenericArguments(); - } - - try { - return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs); - } catch { - // Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll - return null; - } - default: - return null; - } - } - - private byte ReadByte() => _cil[_ptr++]; - - private int ReadInt() { - var b1 = ReadByte(); - var b2 = ReadByte(); - var b3 = ReadByte(); - var b4 = ReadByte(); - return b1 | b2 << 8 | b3 << 16 | b4 << 24; - } - - static ILReader(){ - singleByteOpCode = new OpCode[225]; - doubleByteOpCode = new OpCode[31]; - - var fields = GetOpCodeFields(); - - foreach (var field in fields) { - var code = (OpCode)field.GetValue(null); - if (code.OpCodeType == OpCodeType.Nternal) - continue; - - if (code.Size == 1) - singleByteOpCode[code.Value] = code; - else - doubleByteOpCode[code.Value & 0xff] = code; - } - } - - private static FieldInfo[] GetOpCodeFields() => typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); - } -} diff --git a/src/UnitTests/Core/Impl/Ben.Demystifier/StringBuilderExtensions.cs b/src/UnitTests/Core/Impl/Ben.Demystifier/StringBuilderExtensions.cs deleted file mode 100644 index f2cfa0105..000000000 --- a/src/UnitTests/Core/Impl/Ben.Demystifier/StringBuilderExtensions.cs +++ /dev/null @@ -1,811 +0,0 @@ -// Copyright (c) Ben A Adams. All rights reserved. -// Licensed under the Apache License, Version 2.0. -// -// Python Tools for Visual Studio -// Copyright(c) Microsoft Corporation -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the License); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -// MERCHANTABILITY OR NON-INFRINGEMENT. -// -// See the Apache Version 2.0 License for specific language governing -// permissions and limitations under the License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; -using System.Security; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace TestUtilities.Ben.Demystifier { - public static class StringBuilderExtensions { - public static StringBuilder AppendException(this StringBuilder stringBuilder, Exception exception) { - stringBuilder.Append(exception.Message); - - if (exception.InnerException != null) { - stringBuilder - .Append(" ---> ") - .AppendException(exception.InnerException) - .AppendLine() - .Append(" --- End of inner exception stack trace ---"); - } - - var frames = new StackTrace(exception, true).GetFrames(); - if (frames != null && frames.Length > 0) { - stringBuilder.AppendLine().AppendFrames(frames); - } - - return stringBuilder; - } - - public static StringBuilder AppendFrames(this StringBuilder stringBuilder, StackFrame[] frames) { - if (frames == null || frames.Length == 0) { - return stringBuilder; - } - - for (var i = 0; i < frames.Length; i++) { - var frame = frames[i]; - var method = frame.GetMethod(); - - // Always show last stackFrame - if (!ShowInStackTrace(method) && i != frames.Length - 1) { - continue; - } - - if (i > 0) { - stringBuilder.AppendLine(); - } - - stringBuilder - .Append(" at ") - .AppendMethod(GetMethodDisplay(method)); - - var filePath = frame.GetFileName(); - if (!string.IsNullOrEmpty(filePath) && Uri.TryCreate(filePath, UriKind.Absolute, out var uri)) { - try { - filePath = uri.IsFile ? Path.GetFullPath(filePath) : uri.ToString(); - stringBuilder.Append(" in ").Append(filePath); - } catch (PathTooLongException) { } catch (SecurityException) { } - } - - var lineNo = frame.GetFileLineNumber(); - if (lineNo != 0) { - stringBuilder.Append(":line "); - stringBuilder.Append(lineNo); - } - } - - return stringBuilder; - } - - private static void AppendMethod(this StringBuilder builder, ResolvedMethod method) { - if (method.IsAsync) { - builder.Append("async "); - } - - if (method.ReturnParameter.Type != null) { - builder - .AppendParameter(method.ReturnParameter) - .Append(" "); - } - - var isSubMethodOrLambda = !string.IsNullOrEmpty(method.SubMethod) || method.IsLambda; - - builder - .AppendMethodName(method.Name, method.DeclaringTypeName, isSubMethodOrLambda) - .Append(method.GenericArguments) - .AppendParameters(method.Parameters, method.MethodBase != null); - - if (isSubMethodOrLambda) { - builder - .Append("+") - .Append(method.SubMethod) - .AppendParameters(method.SubMethodParameters, method.SubMethodBase != null); - - if (method.IsLambda) { - builder.Append(" => { }"); - - if (method.Ordinal.HasValue) { - builder.Append(" ["); - builder.Append(method.Ordinal); - builder.Append("]"); - } - } - } - } - - public static StringBuilder AppendMethodName(this StringBuilder stringBuilder, string name, string declaringTypeName, bool isSubMethodOrLambda) { - if (!string.IsNullOrEmpty(declaringTypeName)) { - if (name == ".ctor") { - if (!isSubMethodOrLambda) { - stringBuilder.Append("new "); - } - - stringBuilder.Append(declaringTypeName); - } else if (name == ".cctor") { - stringBuilder - .Append("static ") - .Append(declaringTypeName); - } else { - stringBuilder - .Append(declaringTypeName) - .Append(".") - .Append(name); - } - } else { - stringBuilder.Append(name); - } - - return stringBuilder; - } - - private static void AppendParameters(this StringBuilder stringBuilder, List parameters, bool condition) { - stringBuilder.Append("("); - if (parameters != null) { - if (condition) { - stringBuilder.AppendParameters(parameters); - } else { - stringBuilder.Append("?"); - } - } - stringBuilder.Append(")"); - } - - private static void AppendParameters(this StringBuilder stringBuilder, List parameters) { - var isFirst = true; - foreach (var param in parameters) { - if (isFirst) { - isFirst = false; - } else { - stringBuilder.Append(", "); - } - stringBuilder.AppendParameter(param); - } - } - - private static StringBuilder AppendParameter(this StringBuilder stringBuilder, ResolvedParameter parameter) { - if (!string.IsNullOrEmpty(parameter.Prefix)) { - stringBuilder.Append(parameter.Prefix).Append(" "); - } - - stringBuilder.Append(parameter.Type); - if (!string.IsNullOrEmpty(parameter.Name)) { - stringBuilder.Append(" ").Append(parameter.Name); - } - - return stringBuilder; - } - - private static bool ShowInStackTrace(MethodBase method) { - try { - var type = method.DeclaringType; - if (type == null) { - return true; - } - - if (type == typeof(Task<>) && method.Name == "InnerInvoke") { - return false; - } - - if (type == typeof(Task)) { - switch (method.Name) { - case "ExecuteWithThreadLocal": - case "Execute": - case "ExecutionContextCallback": - case "ExecuteEntry": - case "InnerInvoke": - return false; - } - } - - if (type == typeof(ExecutionContext)) { - switch (method.Name) { - case "RunInternal": - case "Run": - return false; - } - } - - // Don't show any methods marked with the StackTraceHiddenAttribute - // https://github.com/dotnet/coreclr/pull/14652 - foreach (var attibute in method.GetCustomAttributesData()) { - // internal Attribute, match on name - if (attibute.AttributeType.Name == "StackTraceHiddenAttribute") { - return false; - } - } - - foreach (var attibute in type.GetCustomAttributesData()) { - // internal Attribute, match on name - if (attibute.AttributeType.Name == "StackTraceHiddenAttribute") { - return false; - } - } - - // Fallbacks for runtime pre-StackTraceHiddenAttribute - if (type == typeof(ExceptionDispatchInfo) && method.Name == nameof(ExceptionDispatchInfo)) { - return false; - } - - 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; - } - } else if (type.FullName == "System.ThrowHelper") { - return false; - } - } catch { - // GetCustomAttributesData can throw - return true; - } - - return true; - } - - private static ResolvedMethod GetMethodDisplay(MethodBase originMethod) { - var methodDisplayInfo = new ResolvedMethod(); - - // Special case: no method available - if (originMethod == null) { - return methodDisplayInfo; - } - - var method = originMethod; - methodDisplayInfo.SubMethodBase = method; - - // Type name - var type = method.DeclaringType; - var subMethodName = method.Name; - var methodName = method.Name; - - if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) && - (typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))) { - - methodDisplayInfo.IsAsync = typeof(IAsyncStateMachine).IsAssignableFrom(type); - - // Convert StateMachine methods to correct overload +MoveNext() - if (!TryResolveStateMachineMethod(ref method, out type)) { - methodDisplayInfo.SubMethodBase = null; - subMethodName = null; - } - - methodName = method.Name; - } - - // Method name - methodDisplayInfo.MethodBase = method; - methodDisplayInfo.Name = methodName; - if (method.Name.IndexOf("<", StringComparison.Ordinal) >= 0) { - if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal)) { - methodName = method.Name; - methodDisplayInfo.MethodBase = method; - methodDisplayInfo.Name = methodName; - methodDisplayInfo.Ordinal = ordinal; - } else { - methodDisplayInfo.MethodBase = null; - } - - methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod); - - if (methodDisplayInfo.IsLambda && type != null) { - if (methodName == ".cctor") { - var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - foreach (var field in fields) { - var value = field.GetValue(field); - if (value is Delegate d) { - if (ReferenceEquals(d.Method, originMethod) && d.Target.ToString() == originMethod.DeclaringType?.ToString()) { - methodDisplayInfo.Name = field.Name; - methodDisplayInfo.IsLambda = false; - method = originMethod; - break; - } - } - } - } - } - } - - if (subMethodName != methodName) { - methodDisplayInfo.SubMethod = subMethodName; - } - - // ResolveStateMachineMethod may have set declaringType to null - if (type != null) { - var declaringTypeName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true); - methodDisplayInfo.DeclaringTypeName = declaringTypeName; - } - - if (method is MethodInfo mi) { - var returnParameter = mi.ReturnParameter; - if (returnParameter != null) { - methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter); - } else { - methodDisplayInfo.ReturnParameter = new ResolvedParameter(string.Empty - , TypeNameHelper.GetTypeDisplayName(mi.ReturnType, fullName: false, includeGenericParameterNames: true) - , mi.ReturnType - , string.Empty); - } - } - - if (method.IsGenericMethod) { - var genericArguments = method.GetGenericArguments(); - var genericArgumentsString = string.Join(", ", genericArguments - .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); - methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">"; - methodDisplayInfo.ResolvedGenericArguments = genericArguments; - } - - // Method parameters - var parameters = method.GetParameters(); - if (parameters.Length > 0) { - var resolvedParameters = new List(parameters.Length); - for (var i = 0; i < parameters.Length; i++) { - resolvedParameters.Add(GetParameter(parameters[i])); - } - methodDisplayInfo.Parameters = resolvedParameters; - } - - if (methodDisplayInfo.SubMethodBase == methodDisplayInfo.MethodBase) { - methodDisplayInfo.SubMethodBase = null; - } else if (methodDisplayInfo.SubMethodBase != null) { - parameters = methodDisplayInfo.SubMethodBase.GetParameters(); - if (parameters.Length > 0) { - var parameterList = new List(parameters.Length); - foreach (var parameter in parameters) { - var param = GetParameter(parameter); - if (param.Name?.StartsWith("<") ?? true) { - continue; - } - - parameterList.Add(param); - } - - methodDisplayInfo.SubMethodParameters = parameterList; - } - } - - return methodDisplayInfo; - } - - private static bool TryResolveGeneratedName(ref MethodBase method - , out Type type - , out string methodName - , out string subMethodName - , out GeneratedNameKind kind - , out int? ordinal) { - - kind = GeneratedNameKind.None; - type = method.DeclaringType; - subMethodName = null; - ordinal = null; - methodName = method.Name; - - var generatedName = methodName; - - if (!TryParseGeneratedName(generatedName, out kind, out var openBracketOffset, out var closeBracketOffset)) { - return false; - } - - methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); - - switch (kind) { - case GeneratedNameKind.LocalFunction: { - var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); - if (localNameStart < 0) { - break; - } - - localNameStart += 3; - - if (localNameStart < generatedName.Length) { - var localNameEnd = generatedName.IndexOf("|", localNameStart, StringComparison.Ordinal); - if (localNameEnd > 0) { - subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart); - } - } - break; - } - case GeneratedNameKind.LambdaMethod: - subMethodName = ""; - break; - } - - var dt = method.DeclaringType; - if (dt == null) { - return false; - } - - var matchHint = GetMatchHint(kind, method); - var matchName = methodName; - - var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) { - return true; - } - - var candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) { - return true; - } - - const int MaxResolveDepth = 10; - for (var i = 0; i < MaxResolveDepth; i++) { - dt = dt.DeclaringType; - if (dt == null) { - return false; - } - - candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) { - return true; - } - - candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) { - return true; - } - - if (methodName == ".cctor") { - candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - foreach (var cctor in candidateConstructors) { - method = cctor; - type = dt; - return true; - } - } - } - - return false; - } - - private static bool TryResolveSourceMethod(IEnumerable candidateMethods - , GeneratedNameKind kind - , string matchHint - , ref MethodBase method - , ref Type type - , out int? ordinal) { - - ordinal = null; - foreach (var candidateMethod in candidateMethods) { - var methodBody = candidateMethod.GetMethodBody(); - if (methodBody != null && kind == GeneratedNameKind.LambdaMethod) { - foreach (var v in methodBody.LocalVariables) { - if (v.LocalType == type) { - GetOrdinal(method, ref ordinal); - } - method = candidateMethod; - type = method.DeclaringType; - return true; - } - } - - try { - var rawIL = methodBody?.GetILAsByteArray(); - if (rawIL == null) { - continue; - } - - var reader = new ILReader(rawIL); - while (reader.Read(candidateMethod)) { - if (reader.Operand is MethodBase mb) { - if (method == mb || (matchHint != null && method.Name.Contains(matchHint))) { - if (kind == GeneratedNameKind.LambdaMethod) { - GetOrdinal(method, ref ordinal); - } - - method = candidateMethod; - type = method.DeclaringType; - return true; - } - } - } - } catch { - // https://github.com/benaadams/Ben.Demystifier/issues/32 - // Skip methods where il can't be interpreted - } - } - - return false; - } - - private static void GetOrdinal(MethodBase method, ref int? ordinal) { - var lamdaStart = method.Name.IndexOf((char)GeneratedNameKind.LambdaMethod + "__", StringComparison.Ordinal) + 3; - if (lamdaStart > 3) { - var secondStart = method.Name.IndexOf("_", lamdaStart, StringComparison.Ordinal) + 1; - if (secondStart > 0) { - lamdaStart = secondStart; - } - - if (!int.TryParse(method.Name.Substring(lamdaStart), out var foundOrdinal)) { - ordinal = null; - return; - } - - ordinal = foundOrdinal; - - var methods = method.DeclaringType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); - - var startName = method.Name.Substring(0, lamdaStart); - var count = 0; - foreach (var m in methods) { - if (m.Name.Length > lamdaStart && m.Name.StartsWith(startName)) { - count++; - - if (count > 1) { - break; - } - } - } - - if (count <= 1) { - ordinal = null; - } - } - } - - private static string GetMatchHint(GeneratedNameKind kind, MethodBase method) { - var methodName = method.Name; - - switch (kind) { - case GeneratedNameKind.LocalFunction: - var start = methodName.IndexOf("|", StringComparison.Ordinal); - if (start < 1) { - return null; - } - - var end = methodName.IndexOf("_", start, StringComparison.Ordinal) + 1; - if (end <= start) { - return null; - } - - return methodName.Substring(start, end - start); - default: - return null; - } - } - - // Parse the generated name. Returns true for names of the form - // [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain - // generated names, where [middle] and [__[suffix]] are optional, - // and where c is a single character in [1-9a-z] - // (csharp\LanguageAnalysis\LIB\SpecialName.cpp). - private static bool TryParseGeneratedName(string name, out GeneratedNameKind kind, out int openBracketOffset, out int closeBracketOffset) { - openBracketOffset = -1; - if (name.StartsWith("CS$<", StringComparison.Ordinal)) { - openBracketOffset = 3; - } else if (name.StartsWith("<", StringComparison.Ordinal)) { - openBracketOffset = 0; - } - - if (openBracketOffset >= 0) { - closeBracketOffset = IndexOfBalancedParenthesis(name, openBracketOffset, '>'); - if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length) { - int c = name[closeBracketOffset + 1]; - // Note '0' is not special. - if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) { - kind = (GeneratedNameKind)c; - return true; - } - } - } - - kind = GeneratedNameKind.None; - openBracketOffset = -1; - closeBracketOffset = -1; - return false; - } - - private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing) { - var opening = str[openingOffset]; - - var depth = 1; - for (var i = openingOffset + 1; i < str.Length; i++) { - var c = str[i]; - if (c == opening) { - depth++; - } else if (c == closing) { - depth--; - if (depth == 0) { - return i; - } - } - } - - return -1; - } - - private static string GetPrefix(ParameterInfo parameter, Type parameterType) { - if (parameter.IsOut) { - return "out"; - } - - if (parameterType != null && parameterType.IsByRef) { - var attribs = parameter.GetCustomAttributes(inherit: false); - if (attribs?.Length > 0) { - foreach (var attrib in attribs) { - if (attrib is Attribute att && att.GetType().Namespace == "System.Runtime.CompilerServices" && att.GetType().Name == "IsReadOnlyAttribute") { - return "in"; - } - } - } - - return "ref"; - } - - return string.Empty; - } - - private static ResolvedParameter GetParameter(ParameterInfo parameter) { - var parameterType = parameter.ParameterType; - var prefix = GetPrefix(parameter, parameterType); - var parameterTypeString = "?"; - - if (parameterType.IsGenericType) { - var customAttribs = parameter.GetCustomAttributes(inherit: false); - - // We don't use System.ValueTuple yet - - //if (customAttribs.Length > 0) { - // var tupleNames = customAttribs.OfType().FirstOrDefault()?.TransformNames; - - // if (tupleNames?.Count > 0) { - // return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); - // } - //} - } - - if (parameterType.IsByRef) { - parameterType = parameterType.GetElementType(); - } - - parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); - - return new ResolvedParameter(parameter.Name, parameterTypeString, parameterType, prefix); - } - - private static ResolvedParameter GetValueTupleParameter(List tupleNames, string prefix, string name, Type parameterType) { - var sb = new StringBuilder(); - sb.Append("("); - var args = parameterType.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) { - if (i > 0) { - sb.Append(", "); - } - - sb.Append(TypeNameHelper.GetTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true)); - - if (i >= tupleNames.Count) { - continue; - } - - var argName = tupleNames[i]; - if (argName == null) { - continue; - } - - sb.Append(" "); - sb.Append(argName); - } - - sb.Append(")"); - - return new ResolvedParameter(name, sb.ToString(), parameterType, prefix); - } - - 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); - - foreach (var candidateMethod in methods) { - var attributes = candidateMethod.GetCustomAttributes(); - - 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 enum GeneratedNameKind { - None = 0, - - // Used by EE: - ThisProxyField = '4', - HoistedLocalField = '5', - DisplayClassLocalOrField = '8', - LambdaMethod = 'b', - LambdaDisplayClass = 'c', - StateMachineType = 'd', - LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names - - // Used by EnC: - AwaiterField = 'u', - HoistedSynthesizedLocalField = 's', - - // Currently not parsed: - StateMachineStateField = '1', - IteratorCurrentBackingField = '2', - StateMachineParameterProxyField = '3', - ReusableHoistedLocalField = '7', - LambdaCacheField = '9', - FixedBufferField = 'e', - AnonymousType = 'f', - TransparentIdentifier = 'h', - AnonymousTypeField = 'i', - AutoPropertyBackingField = 'k', - IteratorCurrentThreadIdField = 'l', - IteratorFinallyMethod = 'm', - BaseMethodWrapper = 'n', - AsyncBuilderField = 't', - DynamicCallSiteContainerType = 'o', - DynamicCallSiteField = 'p' - } - - private struct ResolvedMethod { - public MethodBase MethodBase { get; set; } - public string DeclaringTypeName { get; set; } - public bool IsAsync { get; set; } - public bool IsLambda { get; set; } - public ResolvedParameter ReturnParameter { get; set; } - public string Name { get; set; } - public int? Ordinal { get; set; } - public string GenericArguments { get; set; } - public Type[] ResolvedGenericArguments { get; set; } - public MethodBase SubMethodBase { get; set; } - public string SubMethod { get; set; } - public List Parameters { get; set; } - public List SubMethodParameters { get; set; } - } - - private struct ResolvedParameter { - public string Name { get; } - public string Type { get; } - public Type ResolvedType { get; } - public string Prefix { get; } - - public ResolvedParameter(string name, string type, Type resolvedType, string prefix) { - Name = name; - Type = type; - ResolvedType = resolvedType; - Prefix = prefix; - } - } - } -} diff --git a/src/UnitTests/Core/Impl/Ben.Demystifier/TypeNameHelper.cs b/src/UnitTests/Core/Impl/Ben.Demystifier/TypeNameHelper.cs deleted file mode 100644 index e3f9eb6cf..000000000 --- a/src/UnitTests/Core/Impl/Ben.Demystifier/TypeNameHelper.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. -// -// Copyright (c) Ben A Adams. All rights reserved. -// Licensed under the Apache License, Version 2.0. -// -// Python Tools for Visual Studio -// Copyright(c) Microsoft Corporation -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the License); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -// MERCHANTABILITY OR NON-INFRINGEMENT. -// -// See the Apache Version 2.0 License for specific language governing -// permissions and limitations under the License. - -using System; -using System.Collections.Generic; -using System.Text; - -namespace TestUtilities.Ben.Demystifier { - // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs - internal sealed class TypeNameHelper { - private static readonly Dictionary _builtInTypeNames = new Dictionary { - { 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" } - }; - - /// - /// Pretty print a type name. - /// - /// The . - /// true to print a fully qualified name. - /// true to include generic parameter names. - /// The pretty printed type name. - 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.Namespace == nameof(System)) { - builder.Append(type.Name); - } else if (type.IsGenericParameter) { - if (options.IncludeGenericParameterNames) { - builder.Append(type.Name); - } - } - else { - builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); - } - } - - private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) { - var innerType = type; - while (innerType != null && innerType.IsArray){ - innerType = innerType.GetElementType(); - } - - ProcessType(builder, innerType, options); - - while (type != null && 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 && type.DeclaringType != null) { - 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; } - } - } -} diff --git a/src/UnitTests/Core/Impl/Ben.Demystifier/readme.txt b/src/UnitTests/Core/Impl/Ben.Demystifier/readme.txt deleted file mode 100644 index eda6df587..000000000 --- a/src/UnitTests/Core/Impl/Ben.Demystifier/readme.txt +++ /dev/null @@ -1 +0,0 @@ -The folder contains modified version of the Ben.Demystifier (https://github.com/benaadams/Ben.Demystifier) which makes it easier to read modern .net stack traces \ No newline at end of file diff --git a/src/UnitTests/Core/Impl/TaskObserver.cs b/src/UnitTests/Core/Impl/TaskObserver.cs index 73041992f..b4337a5c2 100644 --- a/src/UnitTests/Core/Impl/TaskObserver.cs +++ b/src/UnitTests/Core/Impl/TaskObserver.cs @@ -21,15 +21,16 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using FluentAssertions; using FluentAssertions.Execution; -using TestUtilities.Ben.Demystifier; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestUtilities { internal class TaskObserver { private readonly int _secondsTimeout; private readonly Action _afterTaskCompleted; private readonly TaskCompletionSource _tcs; - private readonly ConcurrentDictionary _stackTraces; + private readonly ConcurrentDictionary _stackTraces; private int _count; private bool _isTestCompleted; @@ -37,18 +38,23 @@ public TaskObserver(int secondsTimeout) { _secondsTimeout = secondsTimeout; _afterTaskCompleted = AfterTaskCompleted; _tcs = new TaskCompletionSource(); - _stackTraces = new ConcurrentDictionary(); + _stackTraces = new ConcurrentDictionary(); } - public void Add(Task task) { + public bool TryAdd(Task task) { + if (!_stackTraces.TryAdd(task, GetFilteredStackTrace())) { + return false; + } + + Interlocked.Increment(ref _count); // No reason to watch for task if it is completed already if (task.IsCompleted) { - task.GetAwaiter().GetResult(); + AfterTaskCompleted(task); + } else { + task.ContinueWith(_afterTaskCompleted, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } - Interlocked.Increment(ref _count); - _stackTraces.TryAdd(task, GetFilteredStackTrace()); - task.ContinueWith(_afterTaskCompleted, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + return true; } public void WaitForObservedTask() { @@ -62,20 +68,23 @@ public void WaitForObservedTask() { } } + [CustomAssertion] private void Summarize() { - var incompleteTasks = new Queue>(); - var failedTasks = new Queue>(); + var incompleteTasks = new Queue<(Task, StackTrace)>(); + var failedTasks = new Queue(); foreach (var kvp in _stackTraces) { var task = kvp.Key; + var stackTrace = kvp.Value; + if (!task.IsCompleted) { - incompleteTasks.Enqueue(kvp); + incompleteTasks.Enqueue((task, stackTrace)); } else if (task.IsFaulted && task.Exception != null) { var aggregateException = task.Exception.Flatten(); var exception = aggregateException.InnerExceptions.Count == 1 ? aggregateException.InnerException : aggregateException; - failedTasks.Enqueue(new KeyValuePair(kvp.Value, exception)); + failedTasks.Enqueue(exception); } } @@ -99,16 +108,14 @@ private void Summarize() { } while (incompleteTasks.Count > 0) { - var kvp = incompleteTasks.Dequeue(); - var task = kvp.Key; + var (task, stackTrace) = incompleteTasks.Dequeue(); message .Append("Id: ") .Append(task.Id) .Append(", status: ") .Append(task.Status) .AppendLine() - .AppendFrames(kvp.Value) - .AppendLine() + .AppendLine(new EnhancedStackTrace(stackTrace).ToString()) .AppendLine(); } @@ -133,20 +140,15 @@ private void Summarize() { } while (failedTasks.Count > 0) { - var kvp = failedTasks.Dequeue(); + var exception = failedTasks.Dequeue(); message - .Append(kvp.Value.GetType().Name) - .Append(": ") - .AppendException(kvp.Value) - .AppendLine() - .Append(" --- Task stack trace: ---") - .AppendLine() - .AppendFrames(kvp.Key) + .AppendDemystified(exception) .AppendLine() .AppendLine(); } - Execute.Assertion.FailWith(message.ToString()); + Assert.Fail(message.ToString()); + //Execute.Assertion.FailWith(message.ToString()); } private void TestCompleted() { @@ -169,27 +171,23 @@ private void AfterTaskCompleted(Task task) { } } - private static StackFrame[] GetFilteredStackTrace() { - var stackTrace = new StackTrace(2, true).GetFrames() ?? new StackFrame[0]; - var filteredStackTrace = new List(); - var skip = true; - foreach (var frame in stackTrace) { + private static StackTrace GetFilteredStackTrace() { + var skipCount = 2; + var stackTrace = new StackTrace(skipCount, true); + var frames = stackTrace.GetFrames(); + if (frames == null) { + return stackTrace; + } + + foreach (var frame in frames) { + skipCount++; var frameMethod = frame.GetMethod(); - if (skip) { - if (frameMethod.Name == "DoNotWait" && frameMethod.DeclaringType?.Name == "TaskExtensions") { - skip = false; - } - continue; + if (frameMethod.Name == "DoNotWait" && frameMethod.DeclaringType?.Name == "TaskExtensions") { + return new StackTrace(skipCount, true); } - - if (frameMethod.DeclaringType?.Namespace?.StartsWith("Microsoft.VisualStudio.TestPlatform.MSTestFramework", StringComparison.Ordinal) ?? false) { - continue; - } - - filteredStackTrace.Add(frame); } - return filteredStackTrace.ToArray(); + return stackTrace; } } -} \ No newline at end of file +} diff --git a/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs b/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs index be91f43e0..7f94fb7e5 100644 --- a/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs +++ b/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs @@ -18,13 +18,37 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; namespace TestUtilities { public class TestEnvironmentImpl { + private static readonly FieldInfo _stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly FieldInfo _showDialogField = typeof(Debug).GetField("s_ShowDialog", BindingFlags.Static | BindingFlags.NonPublic); + protected internal static TestEnvironmentImpl Instance { get; protected set; } + protected TestEnvironmentImpl() { + TryOverrideShowDialog(); + } + + private static void TryOverrideShowDialog() { + if (_showDialogField != null) { + _showDialogField.SetValue(null, new Action(ThrowAssertException)); + } + } + + private static void ThrowAssertException(string stackTrace, string message, string detailMessage, string errorSource) { + var exception = new Exception(message); + if (_stackTraceStringField != null) { + _stackTraceStringField.SetValue(exception, stackTrace); + } + throw exception; + } + public static TimeSpan Elapsed() => Instance?._stopwatch.Value?.Elapsed ?? new TimeSpan(); public static void TestInitialize(string testFullName, int secondsTimeout = 10) => Instance?.BeforeTestRun(testFullName, secondsTimeout); public static void TestCleanup() => Instance?.AfterTestRun(); @@ -44,11 +68,7 @@ public TestEnvironmentImpl AddAssemblyResolvePaths(params string[] paths) { public bool TryAddTaskToWait(Task task) { var taskObserver = _taskObserver.Value; - if (taskObserver == null) { - return false; - } - taskObserver.Add(task); - return true; + return taskObserver != null && taskObserver.TryAdd(task); } protected virtual void BeforeTestRun(string testFullName, int secondsTimeout) { @@ -95,9 +115,10 @@ protected virtual void AfterTestRun() { } _taskObserver.Value?.WaitForObservedTask(); + } finally { _stopwatch.Value?.Stop(); TestData.ClearTestRunScope(); - } finally { + _stopwatch.Value = null; _taskObserver.Value = null; } diff --git a/src/UnitTests/Core/Impl/UnitTests.Core.csproj b/src/UnitTests/Core/Impl/UnitTests.Core.csproj index 3f8ed1e09..3a1e90721 100644 --- a/src/UnitTests/Core/Impl/UnitTests.Core.csproj +++ b/src/UnitTests/Core/Impl/UnitTests.Core.csproj @@ -7,6 +7,7 @@ +