@@ -86,6 +86,7 @@ class AssemblyResolver :
86
86
/// </summary>
87
87
private readonly object _syncLock = new ( ) ;
88
88
89
+ private Stack < string > ? _currentlyResolvingResources ;
89
90
private bool _disposed ;
90
91
91
92
/// <summary>
@@ -335,7 +336,7 @@ protected virtual
335
336
if ( EqtTrace . IsInfoEnabled )
336
337
{
337
338
EqtTrace . Info (
338
- "AssemblyResolver: {0}: Failed to create assemblyName. Reason:{1} " ,
339
+ "MSTest. AssemblyResolver.OnResolve: Failed to create assemblyName '{0}' . Reason: {1} " ,
339
340
name ,
340
341
ex ) ;
341
342
}
@@ -344,7 +345,7 @@ protected virtual
344
345
return null ;
345
346
}
346
347
347
- DebugEx . Assert ( requestedName != null && ! StringEx . IsNullOrEmpty ( requestedName . Name ) , "AssemblyResolver.OnResolve: requested is null or name is empty!" ) ;
348
+ DebugEx . Assert ( requestedName != null && ! StringEx . IsNullOrEmpty ( requestedName . Name ) , "MSTest. AssemblyResolver.OnResolve: requested is null or name is empty!" ) ;
348
349
349
350
foreach ( string dir in searchDirectorypaths )
350
351
{
@@ -359,15 +360,41 @@ protected virtual
359
360
{
360
361
if ( EqtTrace . IsVerboseEnabled )
361
362
{
362
- EqtTrace . Verbose ( "AssemblyResolver: Searching assembly: {0} in the directory: {1}" , requestedName . Name , dir ) ;
363
+ EqtTrace . Verbose ( "MSTest. AssemblyResolver.OnResolve : Searching assembly ' {0}' in the directory ' {1}' " , requestedName . Name , dir ) ;
363
364
}
364
365
} ) ;
365
366
366
367
foreach ( string extension in new string [ ] { ".dll" , ".exe" } )
367
368
{
368
369
string assemblyPath = Path . Combine ( dir , requestedName . Name + extension ) ;
369
370
371
+ bool isPushed = false ;
372
+ bool isResource = requestedName . Name . EndsWith ( ".resources" , StringComparison . InvariantCulture ) ;
373
+ if ( isResource )
374
+ {
375
+ // Check for recursive resource lookup.
376
+ // This can happen when we are on non-english locale, and we try to load mscorlib.resources
377
+ // (or potentially some other resources). This will trigger a new Resolve and call the method
378
+ // we are currently in. If then some code in this Resolve method (like File.Exists) will again
379
+ // try to access mscorlib.resources it will end up recursing forever.
380
+ if ( _currentlyResolvingResources != null && _currentlyResolvingResources . Count > 0 && _currentlyResolvingResources . Contains ( assemblyPath ) )
381
+ {
382
+ EqtTrace . Info ( "MSTest.AssemblyResolver.OnResolve: Assembly '{0}' is searching for itself recursively '{1}', returning as not found." , name , assemblyPath ) ;
383
+ _resolvedAssemblies [ name ] = null ;
384
+ return null ;
385
+ }
386
+
387
+ _currentlyResolvingResources ??= new Stack < string > ( 4 ) ;
388
+ _currentlyResolvingResources . Push ( assemblyPath ) ;
389
+ isPushed = true ;
390
+ }
391
+
370
392
Assembly ? assembly = SearchAndLoadAssembly ( assemblyPath , name , requestedName , isReflectionOnly ) ;
393
+ if ( isResource && isPushed )
394
+ {
395
+ _currentlyResolvingResources ? . Pop ( ) ;
396
+ }
397
+
371
398
if ( assembly != null )
372
399
{
373
400
return assembly ;
@@ -447,7 +474,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender,
447
474
{
448
475
if ( StringEx . IsNullOrEmpty ( args . Name ) )
449
476
{
450
- Debug . Fail ( "AssemblyResolver.OnResolve: args.Name is null or empty." ) ;
477
+ Debug . Fail ( "MSTest. AssemblyResolver.OnResolve: args.Name is null or empty." ) ;
451
478
return null ;
452
479
}
453
480
@@ -457,7 +484,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender,
457
484
{
458
485
if ( EqtTrace . IsInfoEnabled )
459
486
{
460
- EqtTrace . Info ( "AssemblyResolver: Resolving assembly: {0}. " , args . Name ) ;
487
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Resolving assembly ' {0}' " , args . Name ) ;
461
488
}
462
489
} ) ;
463
490
@@ -469,7 +496,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender,
469
496
{
470
497
if ( EqtTrace . IsInfoEnabled )
471
498
{
472
- EqtTrace . Info ( "AssemblyResolver: Resolving assembly after applying policy: {0}. " , assemblyNameToLoad ) ;
499
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Resolving assembly after applying policy ' {0}' " , assemblyNameToLoad ) ;
473
500
}
474
501
} ) ;
475
502
@@ -488,56 +515,55 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender,
488
515
return assembly ;
489
516
}
490
517
491
- if ( _directoryList != null && _directoryList . Count != 0 )
492
- {
493
- // required assembly is not present in searchDirectories??
494
- // see, if we can find it in user specified search directories.
495
- while ( assembly == null && _directoryList . Count > 0 )
496
- {
497
- // instead of loading whole search directory in one time, we are adding directory on the basis of need
498
- RecursiveDirectoryPath currentNode = _directoryList . Dequeue ( ) ;
499
-
500
- List < string > incrementalSearchDirectory = [ ] ;
518
+ bool isResource = assemblyNameToLoad . EndsWith ( ".resources" , StringComparison . InvariantCulture ) ;
501
519
502
- if ( DoesDirectoryExist ( currentNode . DirectoryPath ) )
503
- {
504
- incrementalSearchDirectory . Add ( currentNode . DirectoryPath ) ;
520
+ // required assembly is not present in searchDirectories??
521
+ // see, if we can find it in user specified search directories.
522
+ while ( assembly == null && _directoryList ? . Count > 0 )
523
+ {
524
+ // instead of loading whole search directory in one time, we are adding directory on the basis of need
525
+ RecursiveDirectoryPath currentNode = _directoryList . Dequeue ( ) ;
505
526
506
- if ( currentNode . IncludeSubDirectories )
507
- {
508
- // Add all its sub-directory in depth first search order.
509
- AddSubdirectories ( currentNode . DirectoryPath , incrementalSearchDirectory ) ;
510
- }
527
+ List < string > incrementalSearchDirectory = [ ] ;
511
528
512
- // Add this directory list in this.searchDirectories so that when we will try to resolve some other
513
- // assembly, then it will look in this whole directory first.
514
- _searchDirectories . AddRange ( incrementalSearchDirectory ) ;
529
+ if ( DoesDirectoryExist ( currentNode . DirectoryPath ) )
530
+ {
531
+ incrementalSearchDirectory . Add ( currentNode . DirectoryPath ) ;
515
532
516
- assembly = SearchAssembly ( incrementalSearchDirectory , assemblyNameToLoad , isReflectionOnly ) ;
517
- }
518
- else
533
+ if ( currentNode . IncludeSubDirectories )
519
534
{
520
- // generate warning that path does not exist.
521
- SafeLog (
522
- assemblyNameToLoad ,
523
- ( ) =>
524
- {
525
- if ( EqtTrace . IsWarningEnabled )
526
- {
527
- EqtTrace . Warning (
528
- "The Directory: {0}, does not exist" ,
529
- currentNode . DirectoryPath ) ;
530
- }
531
- } ) ;
535
+ // Add all its sub-directory in depth first search order.
536
+ AddSubdirectories ( currentNode . DirectoryPath , incrementalSearchDirectory ) ;
532
537
}
533
- }
534
538
535
- if ( assembly != null )
539
+ // Add this directory list in this.searchDirectories so that when we will try to resolve some other
540
+ // assembly, then it will look in this whole directory first.
541
+ _searchDirectories . AddRange ( incrementalSearchDirectory ) ;
542
+
543
+ assembly = SearchAssembly ( incrementalSearchDirectory , assemblyNameToLoad , isReflectionOnly ) ;
544
+ }
545
+ else
536
546
{
537
- return assembly ;
547
+ // generate warning that path does not exist.
548
+ SafeLog (
549
+ assemblyNameToLoad ,
550
+ ( ) =>
551
+ {
552
+ if ( EqtTrace . IsWarningEnabled )
553
+ {
554
+ EqtTrace . Warning (
555
+ "MSTest.AssemblyResolver.OnResolve: the directory '{0}', does not exist" ,
556
+ currentNode . DirectoryPath ) ;
557
+ }
558
+ } ) ;
538
559
}
539
560
}
540
561
562
+ if ( assembly != null )
563
+ {
564
+ return assembly ;
565
+ }
566
+
541
567
// Try for default load for System dlls that can't be found in search paths. Needs to loaded just by name.
542
568
try
543
569
{
@@ -580,7 +606,7 @@ private void WindowsRuntimeMetadataReflectionOnlyNamespaceResolve(object sender,
580
606
{
581
607
if ( EqtTrace . IsInfoEnabled )
582
608
{
583
- EqtTrace . Info ( "AssemblyResolver: {0}: Failed to load assembly. Reason: {1}" , assemblyNameToLoad , ex ) ;
609
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve: Failed to load assembly '{0}' . Reason: {1}" , assemblyNameToLoad , ex ) ;
584
610
}
585
611
} ) ;
586
612
}
@@ -609,7 +635,7 @@ private bool TryLoadFromCache(string assemblyName, bool isReflectionOnly, out As
609
635
{
610
636
if ( EqtTrace . IsInfoEnabled )
611
637
{
612
- EqtTrace . Info ( "AssemblyResolver: Resolved: {0}. " , assemblyName ) ;
638
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Resolved ' {0}' " , assemblyName ) ;
613
639
}
614
640
} ) ;
615
641
return true ;
@@ -685,7 +711,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction)
685
711
{
686
712
if ( EqtTrace . IsInfoEnabled )
687
713
{
688
- EqtTrace . Info ( "AssemblyResolver: Resolved assembly: {0}. " , assemblyName ) ;
714
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Resolved assembly ' {0}' " , assemblyName ) ;
689
715
}
690
716
} ) ;
691
717
@@ -699,7 +725,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction)
699
725
{
700
726
if ( EqtTrace . IsInfoEnabled )
701
727
{
702
- EqtTrace . Info ( "AssemblyResolver: Failed to load assembly: {0}. Reason:{1} " , assemblyName , ex ) ;
728
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Failed to load assembly ' {0}' . Reason:{1} " , assemblyName , ex ) ;
703
729
}
704
730
} ) ;
705
731
@@ -717,7 +743,7 @@ private static void SafeLog(string? assemblyName, Action loggerAction)
717
743
{
718
744
if ( EqtTrace . IsInfoEnabled )
719
745
{
720
- EqtTrace . Info ( "AssemblyResolver: Failed to load assembly: {0}. Reason:{1} " , assemblyName , ex ) ;
746
+ EqtTrace . Info ( "MSTest. AssemblyResolver.OnResolve : Failed to load assembly ' {0}' . Reason:{1} " , assemblyName , ex ) ;
721
747
}
722
748
} ) ;
723
749
}
0 commit comments