-
Notifications
You must be signed in to change notification settings - Fork 278
Fix assembly resolution error #2948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5846f54
d1a5098
0073708
577366b
c45f1c2
b8a9fe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,7 @@ class AssemblyResolver : | |
/// </summary> | ||
private readonly object _syncLock = new(); | ||
|
||
private static List<string>? s_currentlyLoading; | ||
private bool _disposed; | ||
|
||
/// <summary> | ||
|
@@ -335,7 +336,7 @@ protected virtual | |
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info( | ||
"AssemblyResolver: {0}: Failed to create assemblyName. Reason:{1} ", | ||
"MSTest.AssemblyResolver.OnResolve: Failed to create assemblyName '{0}'. Reason: {1} ", | ||
name, | ||
ex); | ||
} | ||
|
@@ -344,7 +345,7 @@ protected virtual | |
return null; | ||
} | ||
|
||
DebugEx.Assert(requestedName != null && !StringEx.IsNullOrEmpty(requestedName.Name), "AssemblyResolver.OnResolve: requested is null or name is empty!"); | ||
DebugEx.Assert(requestedName != null && !StringEx.IsNullOrEmpty(requestedName.Name), "MSTest.AssemblyResolver.OnResolve: requested is null or name is empty!"); | ||
|
||
foreach (string dir in searchDirectorypaths) | ||
{ | ||
|
@@ -359,15 +360,39 @@ protected virtual | |
{ | ||
if (EqtTrace.IsVerboseEnabled) | ||
{ | ||
EqtTrace.Verbose("AssemblyResolver: Searching assembly: {0} in the directory: {1}", requestedName.Name, dir); | ||
EqtTrace.Verbose("MSTest.AssemblyResolver.OnResolve: Searching assembly '{0}' in the directory '{1}'", requestedName.Name, dir); | ||
} | ||
}); | ||
|
||
foreach (string extension in new string[] { ".dll", ".exe" }) | ||
{ | ||
string assemblyPath = Path.Combine(dir, requestedName.Name + extension); | ||
|
||
bool isPushed = false; | ||
bool isResource = requestedName.Name.EndsWith(".resources", StringComparison.InvariantCulture); | ||
if (isResource) | ||
{ | ||
// Are we recursively looking up the same resource? Note - our backout code will set | ||
// the ResourceHelper's currentlyLoading stack to null if an exception occurs. | ||
if (s_currentlyLoading != null && s_currentlyLoading.Count > 0 && s_currentlyLoading.LastIndexOf(assemblyPath) != -1) | ||
{ | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Assembly '{0}' is searching for itself recursively '{1}', returning as not found.", name, assemblyPath); | ||
_resolvedAssemblies[name] = null; | ||
return null; | ||
} | ||
|
||
s_currentlyLoading ??= new List<string>(); | ||
s_currentlyLoading.Add(assemblyPath); // Push | ||
isPushed = true; | ||
} | ||
|
||
Assembly? assembly = SearchAndLoadAssembly(assemblyPath, name, requestedName, isReflectionOnly); | ||
if (isResource && isPushed) | ||
{ | ||
DebugEx.Assert(s_currentlyLoading is not null, "_currentlyLoading should not be null"); | ||
s_currentlyLoading.RemoveAt(s_currentlyLoading.Count - 1); // Pop | ||
} | ||
|
||
if (assembly != null) | ||
{ | ||
return assembly; | ||
|
@@ -447,7 +472,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender, | |
{ | ||
if (StringEx.IsNullOrEmpty(args.Name)) | ||
{ | ||
Debug.Fail("AssemblyResolver.OnResolve: args.Name is null or empty."); | ||
Debug.Fail("MSTest.AssemblyResolver.OnResolve: args.Name is null or empty."); | ||
return null; | ||
} | ||
|
||
|
@@ -457,7 +482,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender, | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Resolving assembly: {0}.", args.Name); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Resolving assembly '{0}'", args.Name); | ||
} | ||
}); | ||
|
||
|
@@ -469,7 +494,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender, | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Resolving assembly after applying policy: {0}.", assemblyNameToLoad); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Resolving assembly after applying policy '{0}'", assemblyNameToLoad); | ||
} | ||
}); | ||
|
||
|
@@ -482,62 +507,58 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender, | |
} | ||
|
||
assembly = SearchAssembly(_searchDirectories, assemblyNameToLoad, isReflectionOnly); | ||
|
||
if (assembly != null) | ||
{ | ||
return assembly; | ||
} | ||
|
||
if (_directoryList != null && _directoryList.Count != 0) | ||
// required assembly is not present in searchDirectories?? | ||
// see, if we can find it in user specified search directories. | ||
while (assembly == null && _directoryList?.Count > 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the un-needed if and reduce nesting. |
||
{ | ||
// required assembly is not present in searchDirectories?? | ||
// see, if we can find it in user specified search directories. | ||
while (assembly == null && _directoryList.Count > 0) | ||
{ | ||
// instead of loading whole search directory in one time, we are adding directory on the basis of need | ||
RecursiveDirectoryPath currentNode = _directoryList.Dequeue(); | ||
|
||
List<string> incrementalSearchDirectory = []; | ||
// instead of loading whole search directory in one time, we are adding directory on the basis of need | ||
RecursiveDirectoryPath currentNode = _directoryList.Dequeue(); | ||
|
||
if (DoesDirectoryExist(currentNode.DirectoryPath)) | ||
{ | ||
incrementalSearchDirectory.Add(currentNode.DirectoryPath); | ||
|
||
if (currentNode.IncludeSubDirectories) | ||
{ | ||
// Add all its sub-directory in depth first search order. | ||
AddSubdirectories(currentNode.DirectoryPath, incrementalSearchDirectory); | ||
} | ||
List<string> incrementalSearchDirectory = []; | ||
|
||
// Add this directory list in this.searchDirectories so that when we will try to resolve some other | ||
// assembly, then it will look in this whole directory first. | ||
_searchDirectories.AddRange(incrementalSearchDirectory); | ||
if (DoesDirectoryExist(currentNode.DirectoryPath)) | ||
{ | ||
incrementalSearchDirectory.Add(currentNode.DirectoryPath); | ||
|
||
assembly = SearchAssembly(incrementalSearchDirectory, assemblyNameToLoad, isReflectionOnly); | ||
} | ||
else | ||
if (currentNode.IncludeSubDirectories) | ||
{ | ||
// generate warning that path does not exist. | ||
SafeLog( | ||
assemblyNameToLoad, | ||
() => | ||
{ | ||
if (EqtTrace.IsWarningEnabled) | ||
{ | ||
EqtTrace.Warning( | ||
"The Directory: {0}, does not exist", | ||
currentNode.DirectoryPath); | ||
} | ||
}); | ||
// Add all its sub-directory in depth first search order. | ||
AddSubdirectories(currentNode.DirectoryPath, incrementalSearchDirectory); | ||
} | ||
} | ||
|
||
if (assembly != null) | ||
// Add this directory list in this.searchDirectories so that when we will try to resolve some other | ||
// assembly, then it will look in this whole directory first. | ||
_searchDirectories.AddRange(incrementalSearchDirectory); | ||
|
||
assembly = SearchAssembly(incrementalSearchDirectory, assemblyNameToLoad, isReflectionOnly); | ||
} | ||
else | ||
{ | ||
return assembly; | ||
// generate warning that path does not exist. | ||
SafeLog( | ||
assemblyNameToLoad, | ||
() => | ||
{ | ||
if (EqtTrace.IsWarningEnabled) | ||
{ | ||
EqtTrace.Warning( | ||
"MSTest.AssemblyResolver.OnResolve: the directory '{0}', does not exist", | ||
currentNode.DirectoryPath); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
if (assembly != null) | ||
{ | ||
return assembly; | ||
} | ||
|
||
// Try for default load for System dlls that can't be found in search paths. Needs to loaded just by name. | ||
try | ||
{ | ||
|
@@ -580,7 +601,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender, | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: {0}: Failed to load assembly. Reason: {1}", assemblyNameToLoad, ex); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Failed to load assembly '{0}'. Reason: {1}", assemblyNameToLoad, ex); | ||
} | ||
}); | ||
} | ||
|
@@ -609,7 +630,7 @@ private bool TryLoadFromCache(string assemblyName, bool isReflectionOnly, out As | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Resolved: {0}.", assemblyName); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Resolved '{0}'", assemblyName); | ||
} | ||
}); | ||
return true; | ||
|
@@ -685,7 +706,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction) | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Resolved assembly: {0}. ", assemblyName); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Resolved assembly '{0}'", assemblyName); | ||
} | ||
}); | ||
|
||
|
@@ -699,7 +720,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction) | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Failed to load assembly: {0}. Reason:{1} ", assemblyName, ex); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Failed to load assembly '{0}'. Reason:{1} ", assemblyName, ex); | ||
} | ||
}); | ||
|
||
|
@@ -717,7 +738,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction) | |
{ | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("AssemblyResolver: Failed to load assembly: {0}. Reason:{1} ", assemblyName, ex); | ||
EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Failed to load assembly '{0}'. Reason:{1} ", assemblyName, ex); | ||
} | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,14 +102,8 @@ internal TestSourceHost(string sourceFileName, IRunSettings? runSettings, IFrame | |
/// </summary> | ||
public void SetupHost() | ||
{ | ||
#if NETFRAMEWORK || NET | ||
List<string> resolutionPaths = GetResolutionPaths( | ||
_sourceFileName, | ||
#if NETFRAMEWORK | ||
VSInstallationUtilities.IsCurrentProcessRunningInPortableMode()); | ||
#else | ||
false); | ||
#endif | ||
#if NET | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was getting complex to read so I have split .NET logic from .NET FX logic. We end up with a little bit of duplicated code but a way better readability. |
||
List<string> resolutionPaths = GetResolutionPaths(_sourceFileName, false); | ||
|
||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
|
@@ -125,10 +119,20 @@ public void SetupHost() | |
{ | ||
assemblyResolver.Dispose(); | ||
} | ||
#elif NETFRAMEWORK | ||
List<string> resolutionPaths = GetResolutionPaths(_sourceFileName, VSInstallationUtilities.IsCurrentProcessRunningInPortableMode()); | ||
|
||
#endif | ||
if (EqtTrace.IsInfoEnabled) | ||
{ | ||
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(",", resolutionPaths)); | ||
} | ||
|
||
// NOTE: These 2 lines are super important, see https://github.com/microsoft/testfx/issues/2922 | ||
// It's not entirely clear why but not assigning directly the resolver to the field (or/and) disposing the resolver in | ||
// case of an error in TryAddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver causes the issue. | ||
_parentDomainAssemblyResolver = new AssemblyResolver(resolutionPaths); | ||
Evangelink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_ = TryAddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(_parentDomainAssemblyResolver, Path.GetDirectoryName(_sourceFileName)!); | ||
|
||
#if NETFRAMEWORK | ||
// Case when DisableAppDomain setting is present in runsettings and no child-appdomain needs to be created | ||
if (!_isAppDomainCreationDisabled) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefix all assembly resolver messages with
MSTest
and do a little reword to make it easier when debugging logs to understand if it's MSTest or VSTest assembly resolver being triggered.