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

Add a flush method to the FakeAsync zone spec #755

Merged
merged 1 commit into from
Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions lib/zone-spec/fake-async-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
func: Function;
args: any[];
delay: number;
isPeriodic: boolean;
}

class Scheduler {
Expand All @@ -26,13 +27,21 @@

constructor() {}

scheduleFunction(cb: Function, delay: number, args: any[] = [], id: number = -1): number {
scheduleFunction(
cb: Function, delay: number, args: any[] = [], isPeriodic: boolean = false,
id: number = -1): number {
let currentId: number = id < 0 ? this.nextId++ : id;
let endTime = this._currentTime + delay;

// Insert so that scheduler queue remains sorted by end time.
let newEntry:
ScheduledFunction = {endTime: endTime, id: currentId, func: cb, args: args, delay: delay};
let newEntry: ScheduledFunction = {
endTime: endTime,
id: currentId,
func: cb,
args: args,
delay: delay,
isPeriodic: isPeriodic
};
let i = 0;
for (; i < this._schedulerQueue.length; i++) {
let currentEntry = this._schedulerQueue[i];
Expand Down Expand Up @@ -73,6 +82,31 @@
}
this._currentTime = finalTime;
}

flush(limit: number = 20): number {
const startTime = this._currentTime;
let count = 0;
while (this._schedulerQueue.length > 0) {
count++;
if (count > limit) {
throw new Error(
'flush failed after reaching the limit of ' + limit +
' tasks. Does your code use a polling timeout?');
}
// If the only remaining tasks are periodic, finish flushing.
if (!(this._schedulerQueue.filter(task => !task.isPeriodic).length)) {
break;
}
let current = this._schedulerQueue.shift();
this._currentTime = current.endTime;
let retval = current.func.apply(global, current.args);
if (!retval) {
// Uncaught exception in the current scheduled function. Stop processing the queue.
break;
}
}
return this._currentTime - startTime;
}
}

class FakeAsyncTestZoneSpec implements ZoneSpec {
Expand Down Expand Up @@ -134,7 +168,7 @@
return () => {
// Requeue the timer callback if it's not been canceled.
if (this.pendingPeriodicTimers.indexOf(id) !== -1) {
this._scheduler.scheduleFunction(fn, interval, args, id);
this._scheduler.scheduleFunction(fn, interval, args, true, id);
}
};
}
Expand Down Expand Up @@ -168,7 +202,7 @@
completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id);

// Queue the callback and dequeue the periodic timer only on error.
this._scheduler.scheduleFunction(cb, interval, args);
this._scheduler.scheduleFunction(cb, interval, args, true);
this.pendingPeriodicTimers.push(id);
return id;
}
Expand Down Expand Up @@ -209,6 +243,16 @@
flushErrors();
}

flush(): number {
FakeAsyncTestZoneSpec.assertInZone();
this.flushMicrotasks();
let elapsed = this._scheduler.flush();
if (this._lastError !== null) {
this._resetLastErrorAndThrow();
}
return elapsed;
}

// ZoneSpec implementation below.

name: string;
Expand Down
142 changes: 142 additions & 0 deletions test/zone-spec/fake-async-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,148 @@ describe('FakeAsyncTestZoneSpec', () => {
expect(testZoneSpec.pendingTimers.length).toBe(0);
});

describe('flushing all tasks', () => {
it('should flush all pending timers', () => {
fakeAsyncTestZone.run(() => {
let x = false;
let y = false;
let z = false;

setTimeout(() => {
x = true;
}, 10);
setTimeout(() => {
y = true;
}, 100);
setTimeout(() => {
z = true;
}, 70);

let elapsed = testZoneSpec.flush();

expect(elapsed).toEqual(100);
expect(x).toBe(true);
expect(y).toBe(true);
expect(z).toBe(true);
});
});

it('should flush nested timers', () => {
fakeAsyncTestZone.run(() => {
let x = true;
let y = true;
setTimeout(() => {
x = true;
setTimeout(() => {
y = true;
}, 100);
}, 200);

let elapsed = testZoneSpec.flush();

expect(elapsed).toEqual(300);
expect(x).toBe(true);
expect(y).toBe(true);
});
});

it('should advance intervals', () => {
fakeAsyncTestZone.run(() => {
let x = false;
let y = false;
let z = 0;

setTimeout(() => {
x = true;
}, 50);
setTimeout(() => {
y = true;
}, 141);
setInterval(() => {
z++;
}, 10);

let elapsed = testZoneSpec.flush();

expect(elapsed).toEqual(141);
expect(x).toBe(true);
expect(y).toBe(true);
expect(z).toEqual(14);
});
});

it('should not wait for intervals', () => {
fakeAsyncTestZone.run(() => {
let z = 0;

setInterval(() => {
z++;
}, 10);

let elapsed = testZoneSpec.flush();

expect(elapsed).toEqual(0);
expect(z).toEqual(0);
});
});


it('should process micro-tasks created in timers before next timers', () => {
fakeAsyncTestZone.run(() => {
let log: string[] = [];

Promise.resolve(null).then((_) => log.push('microtask'));

setTimeout(() => {
log.push('timer');
Promise.resolve(null).then((_) => log.push('t microtask'));
}, 20);

let id = setInterval(() => {
log.push('periodic timer');
Promise.resolve(null).then((_) => log.push('pt microtask'));
}, 10);

testZoneSpec.flush();
expect(log).toEqual(
['microtask', 'periodic timer', 'pt microtask', 'timer', 't microtask']);
});
});

it('should throw the exception from tick for error thrown in timer callback', () => {
fakeAsyncTestZone.run(() => {
setTimeout(() => {
throw new Error('timer');
}, 10);
expect(() => {
testZoneSpec.flush();
}).toThrowError('timer');
});
// There should be no pending timers after the error in timer callback.
expect(testZoneSpec.pendingTimers.length).toBe(0);
});

it('should do something reasonable with polling timeouts', () => {
expect(() => {
fakeAsyncTestZone.run(() => {
let z = 0;

let poll = () => {
setTimeout(() => {
z++;
poll();
}, 10);
};

poll();
testZoneSpec.flush();
});
})
.toThrowError(
'flush failed after reaching the limit of 20 tasks. Does your code use a polling timeout?');
});
});

describe('outside of FakeAsync Zone', () => {
it('calling flushMicrotasks should throw exception', () => {
expect(() => {
Expand Down