diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs index 4255641119..a2ce548535 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs @@ -5,9 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.AppContainer; #endif using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -#if NETFRAMEWORK -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; -#endif namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; @@ -79,7 +76,7 @@ public bool IsAssemblyReferenced(AssemblyName assemblyName, string source) { #if NETFRAMEWORK // This loads the dll in a different app domain. We can optimize this to load in the current domain since this code could be run in a new app domain anyway. - bool? utfReference = AssemblyHelper.DoesReferencesAssembly(source, assemblyName); + bool? utfReference = DoesSourceReferenceAssembly(source, assemblyName); // If no reference to UTF don't run discovery. Take conservative approach. If not able to find proceed with discovery. return !utfReference.HasValue || utfReference.Value; @@ -94,6 +91,73 @@ public bool IsAssemblyReferenced(AssemblyName assemblyName, string source) #endif } +#if NETFRAMEWORK + /// + /// Checks whether the source assembly directly references the given assembly. + /// Only the assembly simple name and public key token are matched; version is ignored. + /// Returns if the reference could not be determined. + /// + /// The path to the source assembly to inspect. + /// The assembly to look for in the source's references. + /// if referenced, if not, if undeterminable. + private static bool? DoesSourceReferenceAssembly(string source, AssemblyName referenceAssembly) + { + if (string.IsNullOrEmpty(source) || referenceAssembly is null) + { + return null; + } + + try + { + string? referenceAssemblyName = referenceAssembly.Name; + byte[] referenceAssemblyPublicKeyToken = referenceAssembly.GetPublicKeyToken(); + + // ReflectionOnlyLoadFrom loads from the specified path only (no probing) and does not + // execute any code from the loaded assembly. + var assembly = Assembly.ReflectionOnlyLoadFrom(source); + + foreach (AssemblyName referencedAssembly in assembly.GetReferencedAssemblies()) + { + // Match without version: only the simple name and public key token. + if (!string.Equals(referencedAssembly.Name, referenceAssemblyName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (ArePublicKeyTokensEqual(referencedAssembly.GetPublicKeyToken(), referenceAssemblyPublicKeyToken)) + { + return true; + } + } + + return false; + } + catch + { + // Return null if we are not able to check. + return null; + } + } + + private static bool ArePublicKeyTokensEqual(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + + for (int i = 0; i < left.Length; ++i) + { + if (left[i] != right[i]) + { + return false; + } + } + + return true; + } +#endif + /// /// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package (appx), return the file (dll/exe) that contains tests from it. ///