Skip to content
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

test(core): Add benchpress test for transplanted view change detection #36001

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions modules/benchmarks/src/change_detection/BUILD.bazel
@@ -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; }
atscott marked this conversation as resolved.
Show resolved Hide resolved
}

@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);
}
21 changes: 21 additions & 0 deletions modules/benchmarks/src/change_detection/util.ts
@@ -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;
}