test(core): Add benchpress test for transplanted view change detection (#36001)
Adds a performance benchmark for transplanted views (those which are declared in one component and inserted into another). PR Close #36001
This commit is contained in:
parent
372a56a0c9
commit
63815b54f3
|
@ -0,0 +1,32 @@
|
||||||
|
load("//tools:defaults.bzl", "ts_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "util_lib",
|
||||||
|
srcs = ["util.ts"],
|
||||||
|
tsconfig = "//modules/benchmarks:tsconfig-e2e.json",
|
||||||
|
deps = ["//modules/benchmarks/src:util_lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "perf_tests_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["change_detection.perf-spec.ts"],
|
||||||
|
tsconfig = "//modules/benchmarks:tsconfig-e2e.json",
|
||||||
|
deps = [
|
||||||
|
"//modules/e2e_util",
|
||||||
|
"@npm//protractor",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "e2e_tests_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["change_detection.e2e-spec.ts"],
|
||||||
|
tsconfig = "//modules/benchmarks:tsconfig-e2e.json",
|
||||||
|
deps = [
|
||||||
|
"//modules/e2e_util",
|
||||||
|
"@npm//protractor",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {$} from 'protractor';
|
||||||
|
|
||||||
|
import {openBrowser, verifyNoBrowserErrors} from '../../../e2e_util/e2e_util';
|
||||||
|
|
||||||
|
describe('change detection benchmark', () => {
|
||||||
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
|
||||||
|
it(`should render and update`, async() => {
|
||||||
|
openBrowser({
|
||||||
|
url: '',
|
||||||
|
ignoreBrowserSynchronization: true,
|
||||||
|
params: [{name: 'viewCount', value: 1}],
|
||||||
|
});
|
||||||
|
expect($('#root').getText()).toContain('1');
|
||||||
|
await $('#detectChanges').click();
|
||||||
|
expect($('#root').getText()).toContain('2');
|
||||||
|
await $('#detectChanges').click();
|
||||||
|
expect($('#root').getText()).toContain('3');
|
||||||
|
await $('#destroyDom').click();
|
||||||
|
expect(await $('#root').getText()).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {$} from 'protractor';
|
||||||
|
import {runBenchmark, verifyNoBrowserErrors} from '../../../e2e_util/perf_util';
|
||||||
|
|
||||||
|
interface Worker {
|
||||||
|
id: string;
|
||||||
|
prepare?(): void;
|
||||||
|
work(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateWorker: Worker = {
|
||||||
|
id: 'createOnly',
|
||||||
|
prepare: () => {
|
||||||
|
$('#destroyDom').click();
|
||||||
|
$('#createDom').click();
|
||||||
|
},
|
||||||
|
work: () => $('#detectChanges').click()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// In order to make sure that we don't change the ids of the benchmarks, we need to
|
||||||
|
// determine the current test package name from the Bazel target. This is necessary
|
||||||
|
// because previous to the Bazel conversion, the benchmark test ids contained the test
|
||||||
|
// name. We determine the name of the Bazel package where this test runs from the current test
|
||||||
|
// target. The Bazel target
|
||||||
|
// looks like: "//modules/benchmarks/src/change_detection/{pkg_name}:{target_name}".
|
||||||
|
const testPackageName = process.env['BAZEL_TARGET'] !.split(':')[0].split('/').pop();
|
||||||
|
|
||||||
|
describe('change detection benchmark perf', () => {
|
||||||
|
|
||||||
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
|
||||||
|
[UpdateWorker].forEach((worker) => {
|
||||||
|
describe(worker.id, () => {
|
||||||
|
it(`should run benchmark for ${testPackageName}`, async() => {
|
||||||
|
await runChangeDetectionBenchmark({
|
||||||
|
id: `change_detection.${testPackageName}.${worker.id}`,
|
||||||
|
url: '/',
|
||||||
|
ignoreBrowserSynchronization: true,
|
||||||
|
worker: worker
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function runChangeDetectionBenchmark(
|
||||||
|
config: {id: string, url: string, ignoreBrowserSynchronization?: boolean, worker: Worker}) {
|
||||||
|
return runBenchmark({
|
||||||
|
id: config.id,
|
||||||
|
url: config.url,
|
||||||
|
ignoreBrowserSynchronization: config.ignoreBrowserSynchronization,
|
||||||
|
params: [{name: 'viewCount', value: 10}],
|
||||||
|
prepare: config.worker.prepare,
|
||||||
|
work: config.worker.work
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle", "ts_devserver")
|
||||||
|
load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test")
|
||||||
|
load("//modules/benchmarks:e2e_test.bzl", "e2e_test")
|
||||||
|
|
||||||
|
ng_module(
|
||||||
|
name = "transplanted_views_lib",
|
||||||
|
srcs = [
|
||||||
|
"index_aot.ts",
|
||||||
|
"transplanted_views.ts",
|
||||||
|
],
|
||||||
|
tags = ["ivy-only"],
|
||||||
|
deps = [
|
||||||
|
"//modules/benchmarks/src:util_lib",
|
||||||
|
"//modules/benchmarks/src/change_detection:util_lib",
|
||||||
|
"//packages:types",
|
||||||
|
"//packages/common",
|
||||||
|
"//packages/core",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ng_rollup_bundle(
|
||||||
|
name = "bundle",
|
||||||
|
entry_point = ":index_aot.ts",
|
||||||
|
tags = ["ivy-only"],
|
||||||
|
deps = [
|
||||||
|
":transplanted_views_lib",
|
||||||
|
"@npm//rxjs",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_devserver(
|
||||||
|
name = "devserver",
|
||||||
|
port = 4200,
|
||||||
|
static_files = ["index.html"],
|
||||||
|
tags = ["ivy-only"],
|
||||||
|
deps = [
|
||||||
|
":bundle.min_debug.js",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
benchmark_test(
|
||||||
|
name = "perf",
|
||||||
|
server = ":devserver",
|
||||||
|
tags = ["ivy-only"],
|
||||||
|
deps = ["//modules/benchmarks/src/change_detection:perf_tests_lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
e2e_test(
|
||||||
|
name = "e2e",
|
||||||
|
server = ":devserver",
|
||||||
|
tags = ["ivy-only"],
|
||||||
|
deps = ["//modules/benchmarks/src/change_detection:e2e_tests_lib"],
|
||||||
|
)
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Prevent the browser from requesting any favicon. -->
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2>Params</h2>
|
||||||
|
<form>
|
||||||
|
View Count:
|
||||||
|
<input type="number" name="viewCount" placeholder="viewCount" value="10">
|
||||||
|
<br>
|
||||||
|
<button>Apply</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Render3 Transplanted View Benchmark</h2>
|
||||||
|
<p>
|
||||||
|
<button id="destroyDom">destroyDom</button>
|
||||||
|
<button id="createDom">createDom</button>
|
||||||
|
<button id="detectChanges">detectChanges</button>
|
||||||
|
<button id="detectChangesProfile">profile detectChanges</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<declaration-component id="root"></declaration-component>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--load location for ts_devserver-->
|
||||||
|
<script src="/app_bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import {ɵrenderComponent as renderComponent} from '@angular/core';
|
||||||
|
|
||||||
|
import {bindAction, profile} from '../../util';
|
||||||
|
|
||||||
|
import {DeclarationComponent, createDom, destroyDom, detectChanges} from './transplanted_views';
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
let component: DeclarationComponent;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
component = renderComponent<DeclarationComponent>(DeclarationComponent);
|
||||||
|
bindAction('#destroyDom', () => destroyDom(component));
|
||||||
|
bindAction('#createDom', () => createDom(component));
|
||||||
|
bindAction('#detectChanges', () => detectChanges(component));
|
||||||
|
bindAction(
|
||||||
|
'#detectChangesProfile', profile(() => detectChanges(component), noop, 'detect_changes'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {ChangeDetectionStrategy, Component, Input, NgModule, TemplateRef, ɵdetectChanges} from '@angular/core';
|
||||||
|
import {newArray, numViews} from '../util';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'declaration-component',
|
||||||
|
template: `
|
||||||
|
<ng-template #template>{{trackTemplateRefresh()}}</ng-template>
|
||||||
|
<insertion-component [template]="template" [viewCount]="viewCount"></insertion-component>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class DeclarationComponent {
|
||||||
|
@Input() viewCount = 1;
|
||||||
|
// Tracks number of times the template was executed to ensure it was updated during CD.
|
||||||
|
templateRefreshCount = 0;
|
||||||
|
|
||||||
|
trackTemplateRefresh() {
|
||||||
|
this.templateRefreshCount++;
|
||||||
|
return this.templateRefreshCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'insertion-component',
|
||||||
|
template: `
|
||||||
|
<ng-container *ngFor="let n of views; template: template; trackBy: trackByIndex"></ng-container>
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class InsertionComponent {
|
||||||
|
@Input() template !: TemplateRef<{}>;
|
||||||
|
views: any[] = [];
|
||||||
|
@Input()
|
||||||
|
set viewCount(n: number) { this.views = n > 0 ? newArray<any>(n) : []; }
|
||||||
|
|
||||||
|
// use trackBy to ensure profile isn't affected by the cost to refresh ngFor.
|
||||||
|
trackByIndex(index: number, item: any) { return index; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [DeclarationComponent, InsertionComponent], imports: [CommonModule]})
|
||||||
|
export class TransplantedViewModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyDom(component: DeclarationComponent) {
|
||||||
|
component.templateRefreshCount = 0;
|
||||||
|
component.viewCount = 0;
|
||||||
|
ɵdetectChanges(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDom(component: DeclarationComponent) {
|
||||||
|
component.viewCount = numViews;
|
||||||
|
ɵdetectChanges(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detectChanges(component: DeclarationComponent) {
|
||||||
|
ɵdetectChanges(component);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getIntParameter} from '../util';
|
||||||
|
|
||||||
|
export const numViews = getIntParameter('viewCount');
|
||||||
|
|
||||||
|
export function newArray<T = any>(size: number): T[];
|
||||||
|
export function newArray<T>(size: number, value: T): T[];
|
||||||
|
export function newArray<T>(size: number, value?: T): T[] {
|
||||||
|
const list: T[] = [];
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
list.push(value !);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
Loading…
Reference in New Issue