diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 2c9826e8..ab9bb00e 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -212,7 +212,16 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * @default 5000 * @since 1.3.0 */ - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + var DEFAULT_TIMEOUT_INTERVAL = 5000; + Object.defineProperty(j$, 'DEFAULT_TIMEOUT_INTERVAL', { + get: function() { + return DEFAULT_TIMEOUT_INTERVAL; + }, + set: function(newValue) { + j$.util.validateTimeout(newValue, 'jasmine.DEFAULT_TIMEOUT_INTERVAL'); + DEFAULT_TIMEOUT_INTERVAL = newValue; + } + }); j$.getGlobal = function() { return jasmineGlobal; @@ -726,6 +735,21 @@ getJasmineRequireObj().util = function(j$) { } }; + util.validateTimeout = function(timeout, msgPrefix) { + // Timeouts are implemented with setTimeout, which only supports a limited + // range of values. The limit is unspecified, as is the behavior when it's + // exceeded. But on all currently supported JS runtimes, setTimeout calls + // the callback immediately when the timeout is greater than 2147483647 + // (the maximum value of a signed 32 bit integer). + var max = 2147483647; + + if (timeout > max) { + throw new Error( + (msgPrefix || 'Timeout value') + ' cannot be greater than ' + max + ); + } + }; + return util; }; @@ -2286,6 +2310,11 @@ getJasmineRequireObj().Env = function(j$) { if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } + + if (timeout) { + j$.util.validateTimeout(timeout); + } + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); @@ -2309,6 +2338,10 @@ getJasmineRequireObj().Env = function(j$) { this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); @@ -2373,6 +2406,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 @@ -2382,6 +2420,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 @@ -2391,6 +2434,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, @@ -2401,6 +2449,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 2134dd17..566be5eb 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -256,6 +256,12 @@ describe('Env', function() { env.it('async', jasmine.getEnv().makeAsyncAwaitFunction()); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.it('huge timeout', function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#xit', function() { @@ -298,6 +304,12 @@ describe('Env', function() { /fit expects a function argument; received \[object (Undefined|DOMWindow|Object)\]/ ); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.fit('huge timeout', function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#beforeEach', function() { @@ -315,6 +327,12 @@ describe('Env', function() { env.beforeEach(jasmine.getEnv().makeAsyncAwaitFunction()); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.beforeEach(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#beforeAll', function() { @@ -332,6 +350,12 @@ describe('Env', function() { env.beforeAll(jasmine.getEnv().makeAsyncAwaitFunction()); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.beforeAll(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#afterEach', function() { @@ -349,6 +373,12 @@ describe('Env', function() { env.afterEach(jasmine.getEnv().makeAsyncAwaitFunction()); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.afterEach(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('#afterAll', function() { @@ -366,6 +396,12 @@ describe('Env', function() { env.afterAll(jasmine.getEnv().makeAsyncAwaitFunction()); }).not.toThrow(); }); + + it('throws an error when the timeout value is too large for setTimeout', function() { + expect(function() { + env.afterAll(function() {}, 2147483648); + }).toThrowError('Timeout value cannot be greater than 2147483647'); + }); }); describe('when not constructed with suppressLoadErrors: true', function() { diff --git a/spec/core/baseSpec.js b/spec/core/baseSpec.js index dd5dd225..0c6ef23a 100644 --- a/spec/core/baseSpec.js +++ b/spec/core/baseSpec.js @@ -141,4 +141,49 @@ describe('base helpers', function() { ); }); }); + + describe('DEFAULT_TIMEOUT_INTERVAL setter', function() { + var max = 2147483647; + + beforeEach(function() { + this.initialValue = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; + }); + + afterEach(function() { + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.initialValue; + }); + + it('accepts only values <= ' + max, function() { + expect(function() { + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = max + 1; + }).toThrowError( + 'jasmine.DEFAULT_TIMEOUT_INTERVAL cannot be greater than ' + max + ); + + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = max; + expect(jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL).toEqual(max); + }); + + it('is consistent with setTimeout in this environment', function(done) { + var f1 = jasmine.createSpy('setTimeout callback for ' + max), + f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1)), + id; + + // Suppress printing of TimeoutOverflowWarning in node + spyOn(console, 'error'); + + id = setTimeout(f1, max); + setTimeout(function() { + clearTimeout(id); + expect(f1).not.toHaveBeenCalled(); + + id = setTimeout(f2, max + 1); + setTimeout(function() { + clearTimeout(id); + expect(f2).toHaveBeenCalled(); + done(); + }); + }); + }); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index a5d19f3d..895579c1 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1245,6 +1245,11 @@ getJasmineRequireObj().Env = function(j$) { if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } + + if (timeout) { + j$.util.validateTimeout(timeout); + } + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); @@ -1268,6 +1273,10 @@ getJasmineRequireObj().Env = function(j$) { this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); @@ -1332,6 +1341,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 @@ -1341,6 +1355,11 @@ getJasmineRequireObj().Env = function(j$) { this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 @@ -1350,6 +1369,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, @@ -1360,6 +1384,11 @@ getJasmineRequireObj().Env = function(j$) { this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); + + if (timeout) { + j$.util.validateTimeout(timeout); + } + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 diff --git a/src/core/base.js b/src/core/base.js index 4b0ff1cd..ee5c0cad 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -43,7 +43,16 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * @default 5000 * @since 1.3.0 */ - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + var DEFAULT_TIMEOUT_INTERVAL = 5000; + Object.defineProperty(j$, 'DEFAULT_TIMEOUT_INTERVAL', { + get: function() { + return DEFAULT_TIMEOUT_INTERVAL; + }, + set: function(newValue) { + j$.util.validateTimeout(newValue, 'jasmine.DEFAULT_TIMEOUT_INTERVAL'); + DEFAULT_TIMEOUT_INTERVAL = newValue; + } + }); j$.getGlobal = function() { return jasmineGlobal; diff --git a/src/core/util.js b/src/core/util.js index 64aedef1..c45e38fe 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -142,5 +142,20 @@ getJasmineRequireObj().util = function(j$) { } }; + util.validateTimeout = function(timeout, msgPrefix) { + // Timeouts are implemented with setTimeout, which only supports a limited + // range of values. The limit is unspecified, as is the behavior when it's + // exceeded. But on all currently supported JS runtimes, setTimeout calls + // the callback immediately when the timeout is greater than 2147483647 + // (the maximum value of a signed 32 bit integer). + var max = 2147483647; + + if (timeout > max) { + throw new Error( + (msgPrefix || 'Timeout value') + ' cannot be greater than ' + max + ); + } + }; + return util; };