fix(ivy): fix proliferation of provider overrides for modules (#29571)
When an @NgModule is imported more than once in the testing module (for example it appears in the imports of more than one module, or if it's literally listed multiple times), then TestBed had a bug where the providers for the module would be overridden many times. This alone was problematic but would not break tests. However, the original value of the providers field of the ngInjectorDef was saved each time, and restored in the same order. Thus, if the provider array was [X], and overrides were applied twice, then the override array would become [X, X'] and then [X, X', X, X']. However, on the second override the state [X, X'] would be stored as original. The array would then be restored to [X] and then [X, X']. Each test, therefore, would continue to double the size of the providers array for the module, eventually exhausting the browser's memory. This commit adds a Set to track when overrides have been applied to a module and refrain from applying them more than once. PR Close #29571
This commit is contained in:
parent
d46a7c8468
commit
7b27009e20
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, Directive, ErrorHandler, Inject, InjectionToken, NgModule, Optional, Pipe, ɵdefineComponent as defineComponent, ɵsetClassMetadata as setClassMetadata, ɵtext as text} from '@angular/core';
|
import {Component, Directive, ErrorHandler, Inject, Injectable, InjectionToken, NgModule, Optional, Pipe, ɵdefineComponent as defineComponent, ɵsetClassMetadata as setClassMetadata, ɵtext as text} from '@angular/core';
|
||||||
import {TestBed, getTestBed} from '@angular/core/testing/src/test_bed';
|
import {TestBed, getTestBed} from '@angular/core/testing/src/test_bed';
|
||||||
import {By} from '@angular/platform-browser';
|
import {By} from '@angular/platform-browser';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
@ -413,5 +413,31 @@ describe('TestBed', () => {
|
||||||
expect(SomeDirective.hasOwnProperty('ngDirectiveDef')).toBeTruthy();
|
expect(SomeDirective.hasOwnProperty('ngDirectiveDef')).toBeTruthy();
|
||||||
expect(SomePipe.hasOwnProperty('ngPipeDef')).toBeTruthy();
|
expect(SomePipe.hasOwnProperty('ngPipeDef')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should clean up overridden providers for modules that are imported more than once',
|
||||||
|
() => {
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class Token {
|
||||||
|
name: string = 'real';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [Token],
|
||||||
|
})
|
||||||
|
class Module {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [Module, Module]});
|
||||||
|
TestBed.overrideProvider(Token, {useValue: {name: 'fake'}});
|
||||||
|
|
||||||
|
expect(TestBed.get(Token).name).toEqual('fake');
|
||||||
|
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
|
||||||
|
// The providers for the module should have been restored to the original array, with
|
||||||
|
// no trace of the overridden providers.
|
||||||
|
expect((Module as any).ngInjectorDef.providers).toEqual([Token]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -111,6 +111,7 @@ export class R3TestBedCompiler {
|
||||||
private providerOverrides: Provider[] = [];
|
private providerOverrides: Provider[] = [];
|
||||||
private rootProviderOverrides: Provider[] = [];
|
private rootProviderOverrides: Provider[] = [];
|
||||||
private providerOverridesByToken = new Map<any, Provider[]>();
|
private providerOverridesByToken = new Map<any, Provider[]>();
|
||||||
|
private moduleProvidersOverridden = new Set<Type<any>>();
|
||||||
|
|
||||||
private testModuleType: NgModuleType<any>;
|
private testModuleType: NgModuleType<any>;
|
||||||
private testModuleRef: NgModuleRef<any>|null = null;
|
private testModuleRef: NgModuleRef<any>|null = null;
|
||||||
|
@ -374,6 +375,11 @@ export class R3TestBedCompiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyProviderOverridesToModule(moduleType: Type<any>): void {
|
private applyProviderOverridesToModule(moduleType: Type<any>): void {
|
||||||
|
if (this.moduleProvidersOverridden.has(moduleType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.moduleProvidersOverridden.add(moduleType);
|
||||||
|
|
||||||
const injectorDef: any = (moduleType as any)[NG_INJECTOR_DEF];
|
const injectorDef: any = (moduleType as any)[NG_INJECTOR_DEF];
|
||||||
if (this.providerOverridesByToken.size > 0) {
|
if (this.providerOverridesByToken.size > 0) {
|
||||||
if (this.hasProviderOverrides(injectorDef.providers)) {
|
if (this.hasProviderOverrides(injectorDef.providers)) {
|
||||||
|
@ -530,6 +536,7 @@ export class R3TestBedCompiler {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.initialNgDefs.clear();
|
this.initialNgDefs.clear();
|
||||||
|
this.moduleProvidersOverridden.clear();
|
||||||
this.restoreComponentResolutionQueue();
|
this.restoreComponentResolutionQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue