Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit 4942b4a

Browse files
committed
feat(trackingZone): Keep track of tasks to see outstanding tasks.
1 parent 1883589 commit 4942b4a

File tree

4 files changed

+150
-2
lines changed

4 files changed

+150
-2
lines changed

Diff for: gulpfile.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,19 @@ gulp.task('build/long-stack-trace-zone.min.js', function(cb) {
9898
});
9999

100100
gulp.task('build/proxy-zone.js', function(cb) {
101-
return generateBrowserScript('./lib/zone-spec/proxy.ts', 'proxy-zone.js', false, cb);
101+
return generateBrowserScript('./lib/zone-spec/proxy-zone.ts', 'proxy-zone.js', false, cb);
102102
});
103103

104104
gulp.task('build/proxy-zone.min.js', function(cb) {
105-
return generateBrowserScript('./lib/zone-spec/proxy.ts', 'proxy-zone.min.js', true, cb);
105+
return generateBrowserScript('./lib/zone-spec/proxy-zone.ts', 'proxy-zone.min.js', true, cb);
106+
});
107+
108+
gulp.task('build/task-tracking.js', function(cb) {
109+
return generateBrowserScript('./lib/zone-spec/task-tracking.ts', 'task-tracking.js', false, cb);
110+
});
111+
112+
gulp.task('build/task-tracking.min.js', function(cb) {
113+
return generateBrowserScript('./lib/zone-spec/task-tracking.ts', 'task-tracking.min.js', true, cb);
106114
});
107115

108116
gulp.task('build/wtf.js', function(cb) {
@@ -136,6 +144,8 @@ gulp.task('build', [
136144
'build/long-stack-trace-zone.min.js',
137145
'build/proxy-zone.js',
138146
'build/proxy-zone.min.js',
147+
'build/task-tracking.js',
148+
'build/task-tracking.min.js',
139149
'build/wtf.js',
140150
'build/wtf.min.js',
141151
'build/async-test.js',

Diff for: lib/zone-spec/task-tracking.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* A `TaskTrackingZoneSpec` allows one to track all outstanding Tasks.
3+
*
4+
* This is useful in tests. For example to see which tasks are preventing a test from completing
5+
* or an automated way of releasing all of the event listeners at the end of the test.
6+
*/
7+
class TaskTrackingZoneSpec implements ZoneSpec {
8+
name = 'TaskTrackingZone';
9+
microTasks: Task[] = [];
10+
macroTasks: Task[] = [];
11+
eventTasks: Task[] = [];
12+
properties: {[key: string]: any} = {'TaskTrackingZone': this};
13+
14+
static get() {
15+
return Zone.current.get('TaskTrackingZone');
16+
}
17+
18+
private getTasksFor(type: string): Task [] {
19+
switch (type) {
20+
case 'microTask': return this.microTasks;
21+
case 'macroTask': return this.macroTasks;
22+
case 'eventTask': return this.eventTasks;
23+
}
24+
throw new Error('Unknown task format: ' + type);
25+
}
26+
27+
onScheduleTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): Task {
28+
task['creationLocation'] = new Error(`Task '${task.type}' from '${task.source}'.`);
29+
const tasks = this.getTasksFor(task.type);
30+
tasks.push(task);
31+
return parentZoneDelegate.scheduleTask(targetZone, task);
32+
}
33+
34+
onCancelTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): any {
35+
const tasks = this.getTasksFor(task.type);
36+
for(var i = 0; i < tasks.length; i++) {
37+
if (tasks[i] == task) {
38+
tasks.splice(i, 1);
39+
break;
40+
}
41+
}
42+
return parentZoneDelegate.cancelTask(targetZone, task);
43+
}
44+
45+
onInvokeTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
46+
task: Task, applyThis: any, applyArgs: any): any
47+
{
48+
if (task.type === 'eventTask') return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
49+
const tasks = this.getTasksFor(task.type);
50+
for(var i = 0; i < tasks.length; i++) {
51+
if (tasks[i] == task) {
52+
tasks.splice(i, 1);
53+
break;
54+
}
55+
}
56+
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
57+
}
58+
59+
clearEvents() {
60+
while (this.eventTasks.length) {
61+
Zone.current.cancelTask(this.eventTasks[0]);
62+
}
63+
}
64+
}
65+
66+
// Export the class so that new instances can be created with proper
67+
// constructor params.
68+
Zone['TaskTrackingZoneSpec'] = TaskTrackingZoneSpec;

Diff for: test/common_tests.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ import './zone-spec/async-test.spec';
99
import './zone-spec/sync-test.spec';
1010
import './zone-spec/fake-async-test.spec';
1111
import './zone-spec/proxy.spec';
12+
import './zone-spec/task-tracking.spec';

Diff for: test/zone-spec/task-tracking.spec.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import '../../lib/zone-spec/task-tracking';
2+
3+
describe('TaskTrackingZone', function() {
4+
let _TaskTrackingZoneSpec: typeof TaskTrackingZoneSpec = Zone['TaskTrackingZoneSpec'];
5+
let taskTrackingZoneSpec: TaskTrackingZoneSpec = null;
6+
let taskTrackingZone: Zone;
7+
8+
beforeEach(() => {
9+
taskTrackingZoneSpec = new _TaskTrackingZoneSpec();
10+
taskTrackingZone = Zone.current.fork(taskTrackingZoneSpec);
11+
});
12+
13+
it('should track tasks', (done: Function) => {
14+
taskTrackingZone.run(() => {
15+
const microTask = taskTrackingZone.scheduleMicroTask('test1', () => {});
16+
expect(taskTrackingZoneSpec.microTasks.length).toBe(1);
17+
expect(taskTrackingZoneSpec.microTasks[0].source).toBe('test1');
18+
19+
const macroTask = setTimeout(() => {}) as any as Task;
20+
expect(taskTrackingZoneSpec.macroTasks.length).toBe(1);
21+
expect(taskTrackingZoneSpec.macroTasks[0].source).toBe('setTimeout');
22+
taskTrackingZone.cancelTask(macroTask);
23+
expect(taskTrackingZoneSpec.macroTasks.length).toBe(0);
24+
25+
setTimeout(() => {
26+
// assert on execution it is null
27+
expect(taskTrackingZoneSpec.macroTasks.length).toBe(0);
28+
expect(taskTrackingZoneSpec.microTasks.length).toBe(0);
29+
30+
const xhr = new XMLHttpRequest();
31+
xhr.open('get', '/', true);
32+
xhr.onreadystatechange = () => {
33+
if (xhr.readyState == 4) {
34+
// clear current event tasks using setTimeout
35+
setTimeout(() => {
36+
expect(taskTrackingZoneSpec.macroTasks.length).toBe(0);
37+
expect(taskTrackingZoneSpec.microTasks.length).toBe(0);
38+
expect(taskTrackingZoneSpec.eventTasks.length).toBe(2);
39+
taskTrackingZoneSpec.clearEvents();
40+
expect(taskTrackingZoneSpec.eventTasks.length).toBe(0);
41+
done();
42+
});
43+
}
44+
};
45+
xhr.send();
46+
expect(taskTrackingZoneSpec.macroTasks.length).toBe(1);
47+
expect(taskTrackingZoneSpec.macroTasks[0].source).toBe('XMLHttpRequest.send');
48+
49+
expect(taskTrackingZoneSpec.eventTasks.length).toBe(2);
50+
// one for me
51+
expect(taskTrackingZoneSpec.eventTasks[0].source).toBe('XMLHttpRequest.addEventListener:readystatechange');
52+
// one for internall tracking of XHRs.
53+
expect(taskTrackingZoneSpec.eventTasks[1].source).toBe('XMLHttpRequest.addEventListener:readystatechange');
54+
});
55+
56+
});
57+
});
58+
59+
it('should capture task creation stacktrace', (done) => {
60+
taskTrackingZone.run(() => {
61+
const task = setTimeout(() => {
62+
done();
63+
}) as any as Task;
64+
expect(task['creationLocation']).toBeTruthy();
65+
});
66+
});
67+
});
68+
69+
export var __something__;

0 commit comments

Comments
 (0)