Repository: sinonjs/lolex (js)

Issue:
High-resolution timer gets tripped up by floating point arithmetic
* Lolex version : 2.7.4

**What did you expect to happen?**

Advancing the clock with fractional milliseconds should give me an integer nanosecond value when I call `process.hrtime(prev)`.

**What actually happens**

The nanosecond value is a float, not an integer.

**How to reproduce**

```js
const clock = lolex.install()
clock.tick(1022.7791)
clock.hrtime([0, 20000000]) // [ 1, 2779099.999999881 ]
```

The reproduction advanced the clock by a fraction of a nanosecond. I suppose it'd be fine to round the computed nanosecond value, but I'm not sure if it should be rounded up or down. Alternatively something like https://www.npmjs.com/package/decimal.js may help to avoid the floating point arithmetic. Perhaps `tick()` should round down to the closest nanosecond, but I'm not sure if that'll help or not.

Pull request description:
Introduce nanosecond precision ticks
#### Purpose 
This is a fix for #207, which dealt in round-off errors. 

Other notable changes are:
- removes `clock.hrNow`. Should not be an issue as it's an internal detail not documented in our API
- throws on negative ticks. This is a breaking API change, but I would be very surprised if it affected anyone, as this essentially means backwards time travel.
- change some test structure, replacing `if()` blocks with conditional `this.skip()`

#### Background
This PR fixes #207 by doing away with floats for representing time; opting to store milliseconds and millisecond decimals separately. We could have avoided the complications of storing two parts by opting to store all ticks as nanoseconds, but that would pose other round-off issues when adding/subtracting high-value milliseconds. Javascript is unable to represent the epoch as nanos due to too few bits for the `Number` type. See discussion in #207 for more background. The test cases found through that issue is included in the test suite.

I tried a few approaches, but this approach was the simplest one to follow and allowed me to do away with a bit of code. For a while I had problems with the behaviour that setting system time should not affect the hrtime, and I tried recreating the approach of using a separate clock for `hrtime` (which was the purpose `hrNow` served), but keeping the two clocks in sync, including the nanosecond offsets, with all the existing `updateHrTime()` callsites was just too much for my simple mind to fathom :boom: Instead I found a simpler solution which was just to record the delta between the new and old time when a new system clock is set and use that to compensate in the `hrtime()` function (see ae83b27).

closes #207

#### Verifying
1. clone the repo and checkout the branch
1. `npm install && npm test`
1. `npm run test-cloud` (if you have sauce labs credentials)
1. `npm link` - will install this as your local `lolex`
1. Require `lolex` in your repo and run against your own code/tests

Patch:
diff --git a/Readme.md b/Readme.md
index 020781b..dcbfdfc 100644
--- a/Readme.md
+++ b/Readme.md
@@ -218,9 +218,6 @@ milliseconds to advance the clock by or a human-readable string. Valid string
 formats are `"08"` for eight seconds, `"01:00"` for one minute and `"02:34:10"`
 for two hours, 34 minutes and ten seconds.
 
-`time` may be negative, which causes the clock to change but won't fire any
-callbacks.
-
 ### `clock.next()`
 
 Advances the clock to the the moment of the first scheduled timer, firing it.
diff --git a/src/lolex-src.js b/src/lolex-src.js
index 7b9999c..d219a71 100644
--- a/src/lolex-src.js
+++ b/src/lolex-src.js
@@ -91,17 +91,19 @@ function withGlobal(_global) {
     }
 
     /**
-     * Floor function that also works for negative numbers
+     * Get the decimal part of the millisecond value as nanoseconds
+     *
+     * @param {Number} msFloat the number of milliseconds
+     * @returns {Number} an integer number of nanoseconds in the range [0,1e6)
+     *
+     * Example: nanoRemainer(123.456789) -> 456789
      */
-    function fixedFloor(n) {
-        return (n >= 0 ? Math.floor(n) : Math.ceil(n));
-    }
+    function nanoRemainder(msFloat) {
+        var modulo = 1e6;
+        var remainder = (msFloat * 1e6) % modulo;
+        var positiveRemainder = remainder < 0 ? remainder + modulo : remainder;
 
-    /**
-     * % operator that also works for negative numbers
-     */
-    function fixedModulo(n, m) {
-        return Math.round(((n % m) + m) % m);
+        return Math.floor(positiveRemainder);
     }
 
     /**
@@ -505,12 +507,14 @@ function withGlobal(_global) {
     };
 
     /**
-     * @param start {Date|number} the system time
+     * @param start {Date|number} the system time - non-integer values are floored
      * @param loopLimit {number}  maximum number of timers that will be run when calling runAll()
      */
     function createClock(start, loopLimit) {
-        start = start || 0;
+        start = Math.floor(getEpoch(start));
         loopLimit = loopLimit || 1000;
+        var nanos = 0;
+        var adjustedSystemTime = [0, 0]; // [millis, nanoremainder]
 
         if (NativeDate === undefined) {
             throw new Error("The global scope doesn't have a `Date` object"
@@ -518,8 +522,7 @@ function withGlobal(_global) {
         }
 
         var clock = {
-            now: getEpoch(start),
-            hrNow: 0,
+            now: start,
             timeouts: {},
             Date: createDate(),
             loopLimit: loopLimit
@@ -531,6 +534,30 @@ function withGlobal(_global) {
             return 16 - ((clock.now - start) % 16);
         }
 
+        function hrtime(prev) {
+            var millisSinceStart = clock.now - adjustedSystemTime[0] - start;
+            var secsSinceStart = Math.floor( millisSinceStart / 1000);
+            var remainderInNanos = (millisSinceStart - secsSinceStart * 1e3 ) * 1e6 + nanos - adjustedSystemTime[1];
+
+            if (Array.isArray(prev)) {
+                if ( prev[1] > 1e9 ) {
+                    throw new TypeError("Number of nanoseconds can't exceed a billion");
+                }
+
+                var oldSecs = prev[0];
+                var nanoDiff = remainderInNanos - prev[1];
+                var secDiff = secsSinceStart - oldSecs;
+
+                if (nanoDiff < 0) {
+                    nanoDiff += 1e9;
+                    secDiff -= 1;
+                }
+
+                return [secDiff, nanoDiff];
+            }
+            return [secsSinceStart, remainderInNanos];
+        }
+
         clock.setTimeout = function setTimeout(func, timeout) {
             return addTimer(clock, {
                 func: func,
@@ -589,18 +616,32 @@ function withGlobal(_global) {
             return clearTimer(clock, timerId, "AnimationFrame");
         };
 
-        function updateHrTime(newNow) {
-            clock.hrNow += (newNow - clock.now);
-        }
-
         clock.runMicrotasks = function runMicrotasks() {
             runJobs(clock);
         };
 
-        clock.tick = function tick(ms) {
-            ms = typeof ms === "number" ? ms : parseTime(ms);
-            var tickFrom = clock.now;
+        /**
+         * @param {tickValue} {String|Number} number of milliseconds or a human-readable value like "01:11:15"
+        */
+        clock.tick = function tick(tickValue) {
+            var msFloat = typeof tickValue === "number" ? tickValue : parseTime(tickValue);
+            var ms = Math.floor(msFloat);
+            var remainder = nanoRemainder(msFloat);
+            var nanosTotal = nanos + remainder;
             var tickTo = clock.now + ms;
+
+            if (msFloat < 0) {
+                throw new TypeError("Negative ticks are not supported");
+            }
+
+            // adjust for positive overflow
+            if (nanosTotal >= 1e6) {
+                tickTo += 1;
+                nanosTotal -= 1e6;
+            }
+
+            nanos = nanosTotal;
+            var tickFrom = clock.now;
             var previous = clock.now;
             var timer, firstException, oldNow;
 
@@ -619,7 +660,6 @@ function withGlobal(_global) {
             timer = firstTimerInRange(clock, tickFrom, tickTo);
             while (timer && tickFrom <= tickTo) {
                 if (clock.timers[timer.id]) {
-                    updateHrTime(timer.callAt);
                     tickFrom = timer.callAt;
                     clock.now = timer.callAt;
                     oldNow = clock.now;
@@ -662,8 +702,10 @@ function withGlobal(_global) {
                 }
             } else {
                 // no timers remaining in the requested range: move the clock all the way to the end
-                updateHrTime(tickTo);
                 clock.now = tickTo;
+
+                // update nanos
+                nanos = nanosTotal;
             }
             if (firstException) {
                 throw firstException;
@@ -680,7 +722,6 @@ function withGlobal(_global) {
 
             clock.duringTick = true;
             try {
-                updateHrTime(timer.callAt);
                 clock.now = timer.callAt;
                 callTimer(clock, timer);
                 runJobs(clock);
@@ -724,10 +765,10 @@ function withGlobal(_global) {
         };
 
         clock.reset = function reset() {
+            nanos = 0;
             clock.timers = {};
             clock.jobs = [];
-            clock.now = getEpoch(start);
-            clock.hrNow = 0;
+            clock.now = start;
         };
 
         clock.setSystemTime = function setSystemTime(systemTime) {
@@ -736,8 +777,11 @@ function withGlobal(_global) {
             var difference = newNow - clock.now;
             var id, timer;
 
+            adjustedSystemTime[0] = difference;
+            adjustedSystemTime[1] = nanos;
             // update 'system clock'
             clock.now = newNow;
+            nanos = 0;
 
             // update timers and intervals to keep them stable
             for (id in clock.timers) {
@@ -763,28 +807,15 @@ function withGlobal(_global) {
             }
 
             clock.performance.now = function lolexNow() {
-                return clock.hrNow;
+                var hrt = hrtime();
+                var millis = (hrt[0] * 1000 + hrt[1] / 1e6);
+                return millis;
             };
         }
 
+
         if (hrtimePresent) {
-            clock.hrtime = function (prev) {
-                if (Array.isArray(prev)) {
-                    var oldSecs = (prev[0] + prev[1] / 1e9);
-                    var newSecs = (clock.hrNow / 1000);
-                    var difference = (newSecs - oldSecs);
-                    var secs = fixedFloor(difference);
-                    var nanosecs = fixedModulo(difference * 1e9, 1e9);
-                    return [
-                        secs,
-                        nanosecs
-                    ];
-                }
-                return [
-                    fixedFloor(clock.hrNow / 1000),
-                    fixedModulo(clock.hrNow * 1e6, 1e9)
-                ];
-            };
+            clock.hrtime = hrtime;
         }
 
         return clock;


Test patch:
diff --git a/test/lolex-test.js b/test/lolex-test.js
index a809783..2e3fc8b 100644
--- a/test/lolex-test.js
+++ b/test/lolex-test.js
@@ -114,6 +114,93 @@ describe("issue sinon#1852", function () {
     });
 });
 
+describe("issue #207 - nanosecond round-off errors on high-res timer", function () {
+    var clock;
+
+    afterEach(function () {
+        clock.uninstall();
+    });
+
+    if (hrtimePresent) {
+        it("should not round off nanosecond arithmetic on hrtime - case 1", function () {
+            clock = lolex.install();
+
+            clock.tick(1022.7791);
+
+            var nanos = clock.hrtime([0, 2 * 1e7])[1];
+            assert.equals(nanos, 2779100);
+        });
+
+        it("should not round off nanosecond arithmetic on hrtime - case 2", function () {
+            clock = lolex.install({
+                now: new Date("2018-09-12T08:58:33.742000000Z").getTime(),
+                toFake: ["hrtime"]
+            });
+            var start = clock.hrtime();
+            clock.tick(123.493);
+
+            var nanos = clock.hrtime(start)[1];
+            assert.equals(nanos, 123493000);
+        });
+
+        it("should truncate sub-nanosecond ticks", function () {
+            clock = lolex.install();
+            clock.tick(0.123456789);
+
+            var nanos = clock.hrtime()[1];
+            assert.equals(nanos, 123456);
+        });
+    }
+
+    it("should always set 'now' to an integer value when ticking with sub-millisecond precision", function () {
+        clock = lolex.install();
+        clock.tick(2.993);
+
+        assert.equals(clock.now, 2);
+    });
+
+    it("should adjust adjust the 'now' value when the nano-remainder overflows", function () {
+        clock = lolex.install();
+        clock.tick(0.993);
+        clock.tick(.5);
+
+        assert.equals(clock.now, 1);
+    });
+
+    it("should floor negative now values", function () {
+        clock = lolex.install({now: -1.2});
+
+        assert.equals(clock.now, -2);
+    });
+
+    it("should floor start times", function () {
+        clock = lolex.install({now: 1.2});
+        assert.equals(clock.now, 1);
+    });
+
+    it("should floor negative start times", function () {
+        clock = lolex.install({now: -1.2});
+        assert.equals(clock.now, -2);
+    });
+
+    it("should handle ticks on the negative side of the Epoch", function () {
+        clock = lolex.install({now: -2});
+        clock.tick(0.8); // -1.2
+        clock.tick(0.5); // -0.7
+
+        assert.equals(clock.now, -1);
+    });
+
+    it("should handle multiple non-integer ticks", function () {
+        clock = lolex.install({now: -2});
+        clock.tick(1.1); // -0.9
+        clock.tick(0.5);
+        clock.tick(0.5); // 0.1
+
+        assert.equals(clock.now, 0);
+    });
+});
+
 describe("lolex", function () {
 
     describe("setTimeout", function () {
@@ -839,6 +926,13 @@ describe("lolex", function () {
             assert.equals(stub.callCount, 1);
         });
 
+        it("throws on negative ticks", function () {
+            var clock = this.clock;
+
+            assert.exception(function () {
+                clock.tick(-500);
+            }, {message: "Negative ticks are not supported"});
+        });
     });
 
     describe("next", function () {
@@ -1325,11 +1419,13 @@ describe("lolex", function () {
         });
 
         it("resets hrTime - issue #206", function () {
+            if (!hrtimePresent) { this.skip(); }
+
             var clock = lolex.createClock();
             clock.tick(100);
-            assert.equals(clock.hrNow, 100);
+            assert.equals(clock.hrtime(), [0, 100 * 1e6]);
             clock.reset();
-            assert.equals(clock.hrNow, 0);
+            assert.equals(clock.hrtime(), [0, 0]);
         });
     });
 
@@ -2293,314 +2389,325 @@ describe("lolex", function () {
         });
     });
 
-    if (performanceNowPresent) {
-        describe("performance.now()", function () {
-            it("should start at 0", function () {
-                var clock = lolex.createClock(1001);
-                var result = clock.performance.now();
-                assert.same(result, 0);
-            });
+    describe("performance.now()", function () {
 
-            it("should run along with clock.tick", function () {
-                var clock = lolex.createClock(0);
-                clock.tick(5001);
+        before(function () {
+            if (!performanceNowPresent) { this.skip(); }
+        });
+
+        it("should start at 0", function () {
+            var clock = lolex.createClock(1001);
+            var result = clock.performance.now();
+            assert.same(result, 0);
+        });
+
+        it("should run along with clock.tick", function () {
+            var clock = lolex.createClock(0);
+            clock.tick(5001);
+            var result = clock.performance.now();
+            assert.same(result, 5001);
+        });
+
+        it("should listen to multiple ticks in performance.now", function () {
+            var clock = lolex.createClock(0);
+            for (var i = 0; i < 10; i++) {
+                var next = clock.performance.now();
+                assert.same(next, 1000 * i);
+                clock.tick(1000);
+            }
+        });
+
+        it("should run with ticks with timers set", function () {
+            var clock = lolex.createClock(0);
+            clock.setTimeout(function () {
                 var result = clock.performance.now();
-                assert.same(result, 5001);
-            });
+                assert.same(result, 2500);
+            }, 2500);
+            clock.tick(5000);
+        });
 
-            it("should listen to multiple ticks in performance.now", function () {
-                var clock = lolex.createClock(0);
-                for (var i = 0; i < 10; i++) {
-                    var next = clock.performance.now();
-                    assert.same(next, 1000 * i);
-                    clock.tick(1000);
-                }
-            });
+    });
 
-            it("should run with ticks with timers set", function () {
-                var clock = lolex.createClock(0);
-                clock.setTimeout(function () {
-                    var result = clock.performance.now();
-                    assert.same(result, 2500);
-                }, 2500);
-                clock.tick(5000);
-            });
+    describe("process.hrtime()", function () {
 
+        before(function () {
+            if (!hrtimePresent) { this.skip(); }
         });
-    }
-    if (hrtimePresent) {
-        describe("process.hrtime()", function () {
-            afterEach(function () {
-                if (this.clock) {
-                    this.clock.uninstall();
-                }
-            });
 
-            it("should start at 0", function () {
-                var clock = lolex.createClock(1001);
-                var result = clock.hrtime();
-                assert.same(result[0], 0);
-                assert.same(result[1], 0);
-            });
+        afterEach(function () {
+            if (this.clock) {
+                this.clock.uninstall();
+            }
+        });
 
-            it("should run along with clock.tick", function () {
-                var clock = lolex.createClock(0);
-                clock.tick(5001);
-                var prev = clock.hrtime();
-                clock.tick(5001);
-                var result = clock.hrtime(prev);
-                assert.same(result[0], 5);
-                assert.same(result[1], 1000000);
-            });
+        it("should start at 0", function () {
+            var clock = lolex.createClock(1001);
+            var result = clock.hrtime();
+            assert.same(result[0], 0);
+            assert.same(result[1], 0);
+        });
 
-            it("should run along with clock.tick when timers set", function () {
-                var clock = lolex.createClock(0);
-                var prev = clock.hrtime();
-                clock.setTimeout(function () {
-                    var result = clock.hrtime(prev);
-                    assert.same(result[0], 2);
-                    assert.same(result[1], 500000000);
-                }, 2500);
-                clock.tick(5000);
-            });
+        it("should run along with clock.tick", function () {
+            var clock = lolex.createClock(0);
+            clock.tick(5001);
+            var prev = clock.hrtime();
+            clock.tick(5001);
+            var result = clock.hrtime(prev);
+            assert.same(result[0], 5);
+            assert.same(result[1], 1000000);
+        });
 
-            it("should not move with setSystemTime", function () {
-                var clock = lolex.createClock(0);
-                var prev = clock.hrtime();
-                clock.setSystemTime(50000);
+        it("should run along with clock.tick when timers set", function () {
+            var clock = lolex.createClock(0);
+            var prev = clock.hrtime();
+            clock.setTimeout(function () {
                 var result = clock.hrtime(prev);
-                assert.same(result[0], 0);
-                assert.same(result[1], 0);
-            });
+                assert.same(result[0], 2);
+                assert.same(result[1], 500000000);
+            }, 2500);
+            clock.tick(5000);
+        });
 
-            it("should move with timeouts", function () {
-                var clock = lolex.createClock();
-                var result = clock.hrtime();
-                assert.same(result[0], 0);
-                assert.same(result[1], 0);
-                clock.setTimeout(function () {}, 1000);
-                clock.runAll();
-                result = clock.hrtime();
-                assert.same(result[0], 1);
-                assert.same(result[1], 0);
-            });
+        it("should not move with setSystemTime", function () {
+            var clock = lolex.createClock(0);
+            var prev = clock.hrtime();
+            clock.setSystemTime(50000);
+            var result = clock.hrtime(prev);
+            assert.same(result[0], 0);
+            assert.same(result[1], 0);
+        });
 
-            it("should handle floating point", function () {
-                var clock = lolex.createClock();
-                clock.tick(1022.7791);
-                var result = clock.hrtime([0, 20000000]);
+        it("should move with timeouts", function () {
+            var clock = lolex.createClock();
+            var result = clock.hrtime();
+            assert.same(result[0], 0);
+            assert.same(result[1], 0);
+            clock.setTimeout(function () {}, 1000);
+            clock.runAll();
+            result = clock.hrtime();
+            assert.same(result[0], 1);
+            assert.same(result[1], 0);
+        });
 
-                assert.equals(result, [1, 2779100]);
+        it("should handle floating point", function () {
+            var clock = lolex.createClock();
+            clock.tick(1022.7791);
+            var result = clock.hrtime([0, 20000000]);
+
+            assert.equals(result, [1, 2779100]);
+        });
+    });
+
+    describe("microtask semantics", function () {
+
+        before( function () {
+            if (!nextTickPresent) { this.skip(); }
+        });
+
+        it("runs without timers", function () {
+            var clock = lolex.createClock();
+            var called = false;
+            clock.nextTick(function () {
+                called = true;
             });
+            clock.runAll();
+            assert(called);
         });
-    }
-    if (nextTickPresent) {
-        describe("microtask semantics", function () {
-            it("runs without timers", function () {
-                var clock = lolex.createClock();
-                var called = false;
-                clock.nextTick(function () {
-                    called = true;
-                });
-                clock.runAll();
-                assert(called);
+
+        it("runs when runMicrotasks is called on the clock", function () {
+            var clock = lolex.createClock();
+            var called = false;
+            clock.nextTick(function () {
+                called = true;
             });
+            clock.runMicrotasks();
+            assert(called);
+        });
 
-            it("runs when runMicrotasks is called on the clock", function () {
-                var clock = lolex.createClock();
-                var called = false;
+        it("respects loopLimit from below in runMicrotasks", function () {
+            var clock = lolex.createClock(0, 100);
+            for (var i = 0; i < 99; i++) {
+                // eslint-disable-next-line no-loop-func,ie11/no-loop-func
                 clock.nextTick(function () {
-                    called = true;
+                    i--;
                 });
-                clock.runMicrotasks();
-                assert(called);
-            });
+            }
+            clock.runMicrotasks();
+            assert.equals(i, 0);
+        });
 
-            it("respects loopLimit from below in runMicrotasks", function () {
-                var clock = lolex.createClock(0, 100);
-                for (var i = 0; i < 99; i++) {
-                    // eslint-disable-next-line no-loop-func,ie11/no-loop-func
-                    clock.nextTick(function () {
-                        i--;
-                    });
-                }
-                clock.runMicrotasks();
-                assert.equals(i, 0);
-            });
+        it("respects loopLimit from above in runMicrotasks", function () {
+            var clock = lolex.createClock(0, 100);
+            for (var i = 0; i < 120; i++) {
+                // eslint-disable-next-line ie11/no-loop-func
+                clock.nextTick(function () { });
+            }
+            assert.exception(function () { clock.runMicrotasks(); });
+        });
 
-            it("respects loopLimit from above in runMicrotasks", function () {
-                var clock = lolex.createClock(0, 100);
-                for (var i = 0; i < 120; i++) {
-                    // eslint-disable-next-line ie11/no-loop-func
-                    clock.nextTick(function () { });
-                }
-                assert.exception(function () { clock.runMicrotasks(); });
-            });
 
+        it("detects infinite nextTick cycles", function () {
+            var clock = lolex.createClock(0, 1000);
+            clock.nextTick(function repeat() {
+                clock.nextTick(repeat);
+            });
+            assert.exception(function () { clock.runMicrotasks(); });
+        });
 
-            it("detects infinite nextTick cycles", function () {
-                var clock = lolex.createClock(0, 1000);
-                clock.nextTick(function repeat() {
-                    clock.nextTick(repeat);
-                });
-                assert.exception(function () { clock.runMicrotasks(); });
+        it("runs with timers - and before them", function () {
+            var clock = lolex.createClock();
+            var last = "";
+            var called = false;
+            clock.nextTick(function () {
+                called = true;
+                last = "tick";
+            });
+            clock.setTimeout(function () {
+                last = "timeout";
             });
+            clock.runAll();
+            assert(called);
+            assert.equals(last, "timeout");
+        });
 
-            it("runs with timers - and before them", function () {
-                var clock = lolex.createClock();
-                var last = "";
-                var called = false;
-                clock.nextTick(function () {
-                    called = true;
-                    last = "tick";
-                });
-                clock.setTimeout(function () {
-                    last = "timeout";
-                });
-                clock.runAll();
-                assert(called);
-                assert.equals(last, "timeout");
+        it("runs when time is progressed", function () {
+            var clock = lolex.createClock();
+            var called = false;
+            clock.nextTick(function () {
+                called = true;
             });
+            assert(!called);
+            clock.tick(0);
+            assert(called);
+        });
 
-            it("runs when time is progressed", function () {
-                var clock = lolex.createClock();
-                var called = false;
+        it("runs between timers", function () {
+            var clock = lolex.createClock();
+            var order = [];
+            clock.setTimeout(function () {
+                order.push("timer-1");
                 clock.nextTick(function () {
-                    called = true;
+                    order.push("tick");
                 });
-                assert(!called);
-                clock.tick(0);
-                assert(called);
             });
 
-            it("runs between timers", function () {
-                var clock = lolex.createClock();
-                var order = [];
-                clock.setTimeout(function () {
-                    order.push("timer-1");
-                    clock.nextTick(function () {
-                        order.push("tick");
-                    });
-                });
+            clock.setTimeout(function () {
+                order.push("timer-2");
+            });
+            clock.runAll();
+            assert.same(order[0], "timer-1");
+            assert.same(order[1], "tick");
+            assert.same(order[2], "timer-2");
+        });
 
-                clock.setTimeout(function () {
-                    order.push("timer-2");
-                });
-                clock.runAll();
-                assert.same(order[0], "timer-1");
-                assert.same(order[1], "tick");
-                assert.same(order[2], "timer-2");
+        it("installs with microticks", function () {
+            var clock = lolex.install({toFake: ["nextTick"]});
+            var called = false;
+            process.nextTick(function () {
+                called = true;
             });
+            clock.runAll();
+            assert(called);
+            clock.uninstall();
+        });
 
-            it("installs with microticks", function () {
-                var clock = lolex.install({toFake: ["nextTick"]});
-                var called = false;
+        it("installs with microticks and timers in order", function () {
+            var clock = lolex.install({toFake: ["nextTick", "setTimeout"]});
+            var order = [];
+            setTimeout(function () {
+                order.push("timer-1");
                 process.nextTick(function () {
-                    called = true;
+                    order.push("tick");
                 });
-                clock.runAll();
-                assert(called);
-                clock.uninstall();
             });
-
-            it("installs with microticks and timers in order", function () {
-                var clock = lolex.install({toFake: ["nextTick", "setTimeout"]});
-                var order = [];
-                setTimeout(function () {
-                    order.push("timer-1");
-                    process.nextTick(function () {
-                        order.push("tick");
-                    });
-                });
-                setTimeout(function () {
-                    order.push("timer-2");
-                });
-                clock.runAll();
-                assert.same(order[0], "timer-1");
-                assert.same(order[1], "tick");
-                assert.same(order[2], "timer-2");
-                clock.uninstall();
+            setTimeout(function () {
+                order.push("timer-2");
             });
+            clock.runAll();
+            assert.same(order[0], "timer-1");
+            assert.same(order[1], "tick");
+            assert.same(order[2], "timer-2");
+            clock.uninstall();
+        });
 
-            it("uninstalls", function () {
-                var clock = lolex.install({toFake: ["nextTick"]});
-                clock.uninstall();
-                var called = false;
-                process.nextTick(function () {
-                    called = true;
-                });
-                clock.runAll();
-                assert(!called);
+        it("uninstalls", function () {
+            var clock = lolex.install({toFake: ["nextTick"]});
+            clock.uninstall();
+            var called = false;
+            process.nextTick(function () {
+                called = true;
             });
+            clock.runAll();
+            assert(!called);
+        });
 
-            it("returns an empty list of timers on immediate uninstall", function () {
-                var clock = lolex.install();
-                var timers = clock.uninstall();
-                assert.equals(timers, []);
-            });
+        it("returns an empty list of timers on immediate uninstall", function () {
+            var clock = lolex.install();
+            var timers = clock.uninstall();
+            assert.equals(timers, []);
+        });
 
 
-            it("returns a timer if uninstalling before it's called", function () {
-                var clock = lolex.install();
-                clock.setTimeout(function () {}, 100);
-                var timers = clock.uninstall();
-                assert.equals(timers.length, 1);
-                assert.equals(timers[0].createdAt, clock.now);
-                assert.equals(timers[0].callAt, clock.now + 100);
-                assert(typeof timers[0].id !== "undefined");
-            });
+        it("returns a timer if uninstalling before it's called", function () {
+            var clock = lolex.install();
+            clock.setTimeout(function () {}, 100);
+            var timers = clock.uninstall();
+            assert.equals(timers.length, 1);
+            assert.equals(timers[0].createdAt, clock.now);
+            assert.equals(timers[0].callAt, clock.now + 100);
+            assert(typeof timers[0].id !== "undefined");
+        });
 
-            it("does not return already executed timers on uninstall", function () {
-                var clock = lolex.install();
-                clock.setTimeout(function () {}, 100);
-                clock.setTimeout(function () {}, 200);
-                clock.tick(100);
-                var timers = clock.uninstall();
-                assert.equals(timers.length, 1);
-                assert.equals(timers[0].createdAt, clock.now - 100);
-                assert.equals(timers[0].callAt, clock.now + 100);
-                assert(typeof timers[0].id !== "undefined");
-            });
+        it("does not return already executed timers on uninstall", function () {
+            var clock = lolex.install();
+            clock.setTimeout(function () {}, 100);
+            clock.setTimeout(function () {}, 200);
+            clock.tick(100);
+            var timers = clock.uninstall();
+            assert.equals(timers.length, 1);
+            assert.equals(timers[0].createdAt, clock.now - 100);
+            assert.equals(timers[0].callAt, clock.now + 100);
+            assert(typeof timers[0].id !== "undefined");
+        });
+
+        it("returns multiple timers on uninstall if created", function () {
+            var clock = lolex.install();
+            for (var i = 0; i < 5; i++) {
+                // yes, it's silly to create a function in a loop. This is a test, we can live with it
+                // eslint-disable-next-line ie11/no-loop-func
+                clock.setTimeout(function () {}, 100 * i);
+            }
+            var timers = clock.uninstall();
+            assert.equals(timers.length, 5);
+            for (i = 0; i < 5; i++) {
+                assert.equals(timers[i].createdAt, clock.now);
+                assert.equals(timers[i].callAt, clock.now + 100 * i);
+            }
+            assert(typeof timers[0].id !== "undefined");
+        });
 
-            it("returns multiple timers on uninstall if created", function () {
-                var clock = lolex.install();
-                for (var i = 0; i < 5; i++) {
-                    // yes, it's silly to create a function in a loop. This is a test, we can live with it
-                    // eslint-disable-next-line ie11/no-loop-func
-                    clock.setTimeout(function () {}, 100 * i);
-                }
-                var timers = clock.uninstall();
-                assert.equals(timers.length, 5);
-                for (i = 0; i < 5; i++) {
-                    assert.equals(timers[i].createdAt, clock.now);
-                    assert.equals(timers[i].callAt, clock.now + 100 * i);
-                }
-                assert(typeof timers[0].id !== "undefined");
-            });
+        it("passes arguments when installed - GitHub#122", function () {
+            var clock = lolex.install({toFake: ["nextTick"]});
+            var called = false;
+            process.nextTick(function (value) {
+                called = value;
+            }, true);
+            clock.runAll();
+            assert(called);
+            clock.uninstall();
+        });
 
-            it("passes arguments when installed - GitHub#122", function () {
-                var clock = lolex.install({toFake: ["nextTick"]});
-                var called = false;
-                process.nextTick(function (value) {
-                    called = value;
-                }, true);
-                clock.runAll();
+        it("does not install by default - GitHub#126", function (done) {
+            var clock = lolex.install();
+            var spy = sinon.spy(clock, "nextTick");
+            var called = false;
+            process.nextTick(function (value) {
+                called = value;
                 assert(called);
+                assert(!spy.called);
                 clock.uninstall();
-            });
-
-            it("does not install by default - GitHub#126", function (done) {
-                var clock = lolex.install();
-                var spy = sinon.spy(clock, "nextTick");
-                var called = false;
-                process.nextTick(function (value) {
-                    called = value;
-                    assert(called);
-                    assert(!spy.called);
-                    clock.uninstall();
-                    done();
-                }, true);
-            });
+                done();
+            }, true);
         });
-    }
+    });
 });