Repository: sinonjs/lolex
Pull request #40 (2016-01-30 15:02:06)

Add process.hrtime()


Patch:
diff --git a/Readme.md b/Readme.md
index 715fe52..ddbca5c 100644
--- a/Readme.md
+++ b/Readme.md
@@ -161,6 +161,9 @@ In browsers a timer ID is returned.
 Clears the timer given the ID or timer object, as long as it was created using
 `setImmediate`.
 
+### `clock.hrtime(prevTime?)`
+Only available in Node.JS, mimicks process.hrtime().
+
 ### `clock.tick(time)`
 
 Advance the clock, firing callbacks if necessary. `time` may be the number of
diff --git a/src/lolex-src.js b/src/lolex-src.js
index 51fd872..c2e42dc 100644
--- a/src/lolex-src.js
+++ b/src/lolex-src.js
@@ -34,6 +34,7 @@
     var NOOP = function () { return undefined; };
     var timeoutResult = setTimeout(NOOP, 0);
     var addTimerReturnsObject = typeof timeoutResult === "object";
+    var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
     clearTimeout(timeoutResult);
 
     var NativeDate = Date;
@@ -70,6 +71,20 @@
         return ms * 1000;
     }
 
+    /**
+     * Floor function that also works for negative numbers
+     */
+    function fixedFloor(n) {
+        return (n >= 0 ? Math.floor(n) : Math.ceil(n));
+    }
+
+    /**
+     * % operator that also works for negative numbers
+     */
+    function fixedModulo(n, m) {
+        return ((n % m) + m) % m;
+    }
+
     /**
      * Used to grok the `now` parameter to createClock.
      */
@@ -321,16 +336,20 @@
         var method,
             i,
             l;
+        var installedHrTime = "_hrtime"; // make jslint happy
 
         for (i = 0, l = clock.methods.length; i < l; i++) {
             method = clock.methods[i];
-
-            if (target[method].hadOwnProperty) {
-                target[method] = clock["_" + method];
+            if (method === "hrtime") {
+                target.process.hrtime = clock[installedHrTime];
             } else {
-                try {
-                    delete target[method];
-                } catch (ignore) {}
+                if (target[method].hadOwnProperty) {
+                    target[method] = clock["_" + method];
+                } else {
+                    try {
+                        delete target[method];
+                    } catch (ignore) {}
+                }
             }
         }
 
@@ -369,7 +388,8 @@
         clearImmediate: global.clearImmediate,
         setInterval: setInterval,
         clearInterval: clearInterval,
-        Date: Date
+        Date: Date,
+        hrtime: global.process.hrtime
     };
 
     var keys = Object.keys || function (obj) {
@@ -390,6 +410,7 @@
     function createClock(now) {
         var clock = {
             now: getEpoch(now),
+            hrNow: 0,
             timeouts: {},
             Date: createDate()
         };
@@ -441,10 +462,16 @@
 
             clock.duringTick = true;
 
+            function updateHrTime(newNow) {
+                clock.hrNow += (newNow - clock.now);
+            }
+
             var firstException;
             while (timer && tickFrom <= tickTo) {
                 if (clock.timers[timer.id]) {
-                    tickFrom = clock.now = timer.callAt;
+                    updateHrTime(timer.callAt);
+                    tickFrom = timer.callAt;
+                    clock.now = timer.callAt;
                     try {
                         oldNow = clock.now;
                         callTimer(clock, timer);
@@ -464,6 +491,7 @@
             }
 
             clock.duringTick = false;
+            updateHrTime(tickTo);
             clock.now = tickTo;
 
             if (firstException) {
@@ -512,6 +540,26 @@
             }
         };
 
+        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)
+                ];
+            };
+        }
+
         return clock;
     }
     exports.createClock = createClock;
@@ -543,7 +591,13 @@
         }
 
         for (i = 0, l = clock.methods.length; i < l; i++) {
-            hijackMethod(target, clock.methods[i], clock);
+            if (clock.methods[i] === "hrtime") {
+                if (target.process && typeof target.process.hrtime === "function") {
+                    hijackMethod(target.process, clock.methods[i], clock);
+                }
+            } else {
+                hijackMethod(target, clock.methods[i], clock);
+            }
         }
 
         return clock;


Test patch:
diff --git a/test/lolex-test.js b/test/lolex-test.js
index 827ce2b..037e070 100644
--- a/test/lolex-test.js
+++ b/test/lolex-test.js
@@ -28,6 +28,7 @@ var assert = referee.assert;
 var refute = referee.refute;
 var GlobalDate = Date;
 var NOOP = function NOOP() { return undefined; };
+var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
 
 describe("lolex", function () {
 
@@ -1349,6 +1350,27 @@ describe("lolex", function () {
             assert.same(clearInterval, lolex.timers.clearInterval);
         });
 
+        if (hrtimePresent) {
+            it("replaces global process.hrtime", function () {
+                this.clock = lolex.install();
+                var prev = process.hrtime();
+                this.clock.tick(1000);
+                var result = process.hrtime(prev);
+                assert.same(result[0], 1);
+                assert.same(result[1], 0);
+            });
+
+            it("uninstalls global process.hrtime", function () {
+                this.clock = lolex.install();
+                this.clock.uninstall();
+                assert.same(process.hrtime, lolex.timers.hrtime);
+                var prev = process.hrtime();
+                this.clock.tick(1000);
+                var result = process.hrtime(prev);
+                assert.same(result[0], 0);
+            });
+        }
+
         // Comment out the next block for jslint to be "happy".
         if (global.__proto__) {
             delete global.hasOwnPropertyTest;
@@ -1459,4 +1481,46 @@ describe("lolex", function () {
             });
         });
     });
+
+    if (hrtimePresent) {
+        describe("process.hrtime()", function() {
+
+            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", 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 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 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);
+            });
+        });
+    }
 });