Skip to content

Commit 98ac36c

Browse files
committed
Fix test-hygiene issues in GeneratedFactoriesTests for #1461
Two tests were interacting with the process-global ReflectionCacheSet.Shared in ways that are unsafe under xUnit's parallel test execution: - FactoryDelegate_CompiledGeneratorCache_ClearedByReflectionCacheSetClear called ReflectionCacheSet.Shared.Clear(), wiping the entire process-wide reflection cache that all other parallel tests depend on. Replaced with a fresh new ReflectionCacheSet() instance: directly adds an entry to the GeneratedFactoryServiceRegistrationGenerators cache, asserts NotEmpty, calls set.Clear(), asserts Empty. Proves the cache participates in clearing without any global side effects. - FactoryDelegate_CompiledGeneratorCached_SiblingScopes asserted .Count stability on the shared cache, which is perturbed by parallel test classes populating other entries. Replaced the count-equality assertion with an identity check: captures the cached delegate reference (TryGetValue) after scope1, then after scope2/scope3 asserts Assert.Same on the same key — proving no recompilation occurred for that specific key regardless of what other tests add to the global cache. Also adds FactoryDelegate_CompiledGeneratorCache_ClearedByReflectionCacheSetPredicateClear, a new test (also using a fresh set instance) that exercises the predicate-based Clear(predicate) path, proving that the Type component of the composite key drives assembly-unload selective eviction while unrelated keys are preserved.
1 parent 020f778 commit 98ac36c

1 file changed

Lines changed: 41 additions & 16 deletions

File tree

test/Autofac.Test/Features/GeneratedFactories/GeneratedFactoriesTests.cs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -475,21 +475,23 @@ public void FactoryDelegate_CompiledGeneratorCached_SiblingScopes()
475475
using var scope1 = container.BeginLifetimeScope(c => c.RegisterType<FuncConsumer1461>());
476476
scope1.Resolve<FuncConsumer1461>();
477477

478-
// After the first resolve, the compiled generator must be present in the cache.
479-
Assert.True(ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators.ContainsKey(cacheKey));
478+
// After the first resolve, the compiled generator must be present in the shared cache.
479+
var cache = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators;
480+
Assert.True(cache.ContainsKey(cacheKey));
480481

481-
var countAfterFirstScope = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators.Count;
482+
// Capture the cached delegate instance — sibling scopes must reuse the same compiled entry.
483+
Assert.True(cache.TryGetValue(cacheKey, out var cachedGeneratorAfterScope1));
482484

483485
using var scope2 = container.BeginLifetimeScope(c => c.RegisterType<FuncConsumer1461>());
484486
scope2.Resolve<FuncConsumer1461>();
485487

486488
using var scope3 = container.BeginLifetimeScope(c => c.RegisterType<FuncConsumer1461>());
487489
scope3.Resolve<FuncConsumer1461>();
488490

489-
// Resolving the same Func<T> from additional sibling scopes must not add new cache entries;
490-
// the count must remain stable after the first scope warms the entry.
491-
var countAfterAllScopes = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators.Count;
492-
Assert.Equal(countAfterFirstScope, countAfterAllScopes);
491+
// The cached delegate for this key must be the same object reference after sibling scope
492+
// resolutions — proving no recompilation occurred for the already-cached key.
493+
Assert.True(cache.TryGetValue(cacheKey, out var cachedGeneratorAfterAllScopes));
494+
Assert.Same(cachedGeneratorAfterScope1, cachedGeneratorAfterAllScopes);
493495
}
494496

495497
[Fact]
@@ -644,19 +646,42 @@ public void FactoryDelegate_CompiledGeneratorCache_ClearedByReflectionCacheSetCl
644646
{
645647
// #1461: The generated-factory caches must participate in ReflectionCacheSet.Clear()
646648
// so that types from collectible AssemblyLoadContexts can be fully unloaded.
647-
var builder = new ContainerBuilder();
648-
builder.RegisterType<Dependency1461>();
649-
using var container = builder.Build();
649+
// Uses a fresh ReflectionCacheSet instance to avoid mutating process-global shared state.
650+
var set = new ReflectionCacheSet();
651+
652+
var cache = set.Internal.GeneratedFactoryServiceRegistrationGenerators;
653+
var key = (typeof(Func<Dependency1461>), Autofac.Features.GeneratedFactories.ParameterMapping.ByType);
654+
cache[key] = null!;
655+
656+
Assert.NotEmpty(cache);
650657

651-
using var scope = container.BeginLifetimeScope(c => c.RegisterType<FuncConsumer1461>());
652-
scope.Resolve<FuncConsumer1461>();
658+
set.Clear();
659+
660+
Assert.Empty(cache);
661+
}
662+
663+
[Fact]
664+
public void FactoryDelegate_CompiledGeneratorCache_ClearedByReflectionCacheSetPredicateClear()
665+
{
666+
// #1461: The generated-factory caches must also participate in predicate-based
667+
// ReflectionCacheSet.Clear(predicate), which is the path taken when a collectible
668+
// AssemblyLoadContext is unloaded — only types belonging to the unloaded assembly
669+
// are removed, leaving unrelated entries intact.
670+
// Uses a fresh ReflectionCacheSet instance to avoid mutating process-global shared state.
671+
var set = new ReflectionCacheSet();
653672

654-
var serviceRegCache = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators;
673+
var cache = set.Internal.GeneratedFactoryServiceRegistrationGenerators;
674+
var matchedKey = (typeof(Dependency1461), Autofac.Features.GeneratedFactories.ParameterMapping.ByType);
675+
var unmatchedKey = (typeof(FuncConsumer1461), Autofac.Features.GeneratedFactories.ParameterMapping.ByName);
676+
cache[matchedKey] = null!;
677+
cache[unmatchedKey] = null!;
655678

656-
Assert.NotEmpty(serviceRegCache);
679+
Assert.Equal(2, cache.Count);
657680

658-
ReflectionCacheSet.Shared.Clear();
681+
// Simulate assembly-unload predicate: only remove entries whose Type key is Dependency1461.
682+
set.Clear((member, _) => member == typeof(Dependency1461));
659683

660-
Assert.Empty(serviceRegCache);
684+
Assert.False(cache.ContainsKey(matchedKey));
685+
Assert.True(cache.ContainsKey(unmatchedKey));
661686
}
662687
}

0 commit comments

Comments
 (0)