Skip to content

Commit d47716a

Browse files
committed
deps: remove timekeeper
The timekeeper package is being used to perform fake time travel in a couple of unit tests. The same can also be easily achieved by using a mock of the JavaScript API resorting to other 3rd-party libraries already being used like testdouble.js. This patch introduces some changes to centralize the most common use of the JavaScript API, in the form of "Date.now()" to a custom "global" utility function as part of the existing system tools. Additionally, it changes the corresponding component unit tests to use a fake version of that function which can be controlled individually for each test and avoids the need to use the timekeeper package. As a consequence, the package was also removed from the list of "devDependencies".
1 parent 7dc066a commit d47716a

File tree

8 files changed

+172
-99
lines changed

8 files changed

+172
-99
lines changed

lib/DevAPI/Connection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ function Connection ({ auth, connectionAttributes = {}, connectTimeout = 10000,
661661
// When a connection becomes unavailable, it should
662662
// include the timestamp of when it was last tried.
663663
const unavailable = Object.assign({}, state.endpoints.available.shift(), {
664-
unavailableAt: Date.now()
664+
unavailableAt: system.time()
665665
});
666666

667667
state.endpoints.unavailable.push(unavailable);
@@ -1238,7 +1238,7 @@ function Connection ({ auth, connectionAttributes = {}, connectTimeout = 10000,
12381238
* @returns {module:Connection}
12391239
*/
12401240
update () {
1241-
const now = Date.now();
1241+
const now = system.time();
12421242
// Check which unavailable endpoints can be re-tried.
12431243
// If the current element in the list is not "retryable", neither
12441244
// are the remaining. A retryable endpoint is any endpoint that has

lib/DevAPI/ConnectionPool.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
const connection = require('./PoolConnection');
3434
const errors = require('../constants/errors');
35+
const system = require('../system');
3536
const util = require('util');
3637
const { isValidBoolean, isValidInteger } = require('../validator');
3738

@@ -172,7 +173,7 @@ function ConnectionPool (options = {}) {
172173
* @throws Will return a rejected Promise if queueTimeout is exceeded.
173174
* @returns {Promise<PoolConnection>}
174175
*/
175-
getConnection (requestedAt = Date.now()) {
176+
getConnection (requestedAt = system.time()) {
176177
return this.update()
177178
.then(() => {
178179
// If previous connection requests were queued and the
@@ -185,7 +186,7 @@ function ConnectionPool (options = {}) {
185186
return;
186187
}
187188

188-
const elapsedTime = Date.now() - requestedAt;
189+
const elapsedTime = system.time() - requestedAt;
189190
const queueTimeout = options.pooling.queueTimeout;
190191

191192
const postConnect = con => {

lib/DevAPI/PoolConnection.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -31,6 +31,7 @@
3131
'use strict';
3232

3333
const connection = require('./Connection');
34+
const system = require('../system');
3435

3536
/**
3637
* API for connections to be used by a connection pool.
@@ -73,7 +74,7 @@ function PoolConnection (options = { pooling: {} }) {
7374
* @returns {Promise} Returns a promise just to keep consistency with standalone connections
7475
*/
7576
close () {
76-
state.releasedAt = Date.now();
77+
state.releasedAt = system.time();
7778
return Promise.resolve();
7879
},
7980

@@ -100,7 +101,7 @@ function PoolConnection (options = { pooling: {} }) {
100101

101102
// Otherwise, we should check if the connection was released for
102103
// more than the value of maxIdleTime.
103-
return Date.now() - state.releasedAt > options.pooling.maxIdleTime;
104+
return system.time() - state.releasedAt > options.pooling.maxIdleTime;
104105
},
105106

106107
/**

lib/system.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -155,3 +155,13 @@ exports.platform = function () {
155155

156156
return convention;
157157
};
158+
159+
/**
160+
* Retrieve the current system time in milliseconds elapsed since the Unix
161+
* epoch.
162+
* @private
163+
* @returns {number}
164+
*/
165+
exports.time = function () {
166+
return Date.now();
167+
};

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@
7171
"nyc": "15.1.0",
7272
"snazzy": "9.0.0",
7373
"standardx": "7.0.0",
74-
"testdouble": "3.16.1",
75-
"timekeeper": "2.2.0"
74+
"testdouble": "3.16.1"
7675
},
7776
"nyc": {
7877
"branches": 75,

test/unit/DevAPI/ConnectionPool.js

Lines changed: 94 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
const errors = require('../../../lib/constants/errors');
3636
const expect = require('chai').expect;
3737
const td = require('testdouble');
38-
const tk = require('timekeeper');
3938
const util = require('util');
4039

4140
// subject under test needs to be reloaded with replacement fakes
@@ -44,7 +43,6 @@ let connectionPool = require('../../../lib/DevAPI/ConnectionPool');
4443
describe('ConnectionPool', () => {
4544
afterEach('reset fakes', () => {
4645
td.reset();
47-
tk.reset();
4846
});
4947

5048
context('create()', () => {
@@ -141,30 +139,6 @@ describe('ConnectionPool', () => {
141139
connectionPool = require('../../../lib/DevAPI/ConnectionPool');
142140
});
143141

144-
it('fails if queueTimeout was exceeded', done => {
145-
const queueTimeout = 5000;
146-
const pool = connectionPool({ pooling: { maxSize: 3, queueTimeout } }).create({ active: ['foo', 'bar', 'baz'] });
147-
const isAvailable = td.replace(pool, 'isAvailable');
148-
const update = td.replace(pool, 'update');
149-
150-
td.when(update()).thenResolve();
151-
td.when(isAvailable()).thenReturn(true);
152-
153-
// We need to travel in time only after calling
154-
// pool.getConnection(), otherwise, Date.now() will also be in the
155-
// future.
156-
pool.getConnection()
157-
.then(() => {
158-
done(expect.fail());
159-
})
160-
.catch(err => {
161-
expect(err.message).to.equal(util.format(errors.MESSAGES.ER_DEVAPI_POOL_QUEUE_TIMEOUT, queueTimeout));
162-
done();
163-
});
164-
165-
tk.travel(new Date(Date.now() + queueTimeout + 1));
166-
});
167-
168142
it('acquires and returns an idle connection if one exists and no expired ones exist', () => {
169143
const poolConnection = { acquire, isClosing, isExpired, isOpen, override };
170144
const pool = connectionPool({ pooling: { maxSize: 3 } }).create({ active: ['foo'], idle: [poolConnection] });
@@ -281,61 +255,109 @@ describe('ConnectionPool', () => {
281255
});
282256
});
283257

284-
it('queues the request when the pool is full and queueTimeout was not exceeded', done => {
285-
const queueTimeout = 1000;
286-
const options = { pooling: { maxSize: 3, queueTimeout } };
287-
const pool = connectionPool(options).create({ active: ['foo', 'bar', 'baz'] });
288-
const isAvailable = td.replace(pool, 'isAvailable');
289-
const update = td.replace(pool, 'update');
290-
const poolConnection = { acquire };
258+
context('when the pool is full', () => {
259+
let system;
291260

292-
td.when(update()).thenResolve();
293-
td.when(isAvailable()).thenReturn(true);
294-
td.when(connection(options)).thenReturn({ open });
295-
td.when(open()).thenResolve(poolConnection);
296-
297-
// We need to travel in time only after calling
298-
// pool.getConnection(), otherwise, Date.now() will also be in the
299-
// future.
300-
pool.getConnection().then(con => {
301-
expect(td.explain(acquire).callCount).to.equal(1);
302-
expect(pool.activeConnections()).to.deep.include(con);
303-
expect(con).to.deep.equal(poolConnection);
261+
beforeEach('setup fake time', () => {
262+
system = td.replace('../../../lib/system');
304263

305-
return done();
264+
connectionPool = require('../../../lib/DevAPI/ConnectionPool');
306265
});
307266

308-
tk.travel(new Date(Date.now() + queueTimeout / 10 + 1));
309-
// We reset the pool after queueTimeout / 10.
310-
setTimeout(() => pool.reset(), queueTimeout / 10);
311-
});
312-
313-
it('queues the request when an an expired connection is available but is still being closed and queueTimeout was not exceeded', done => {
314-
const queueTimeout = 1000;
315-
const options = { pooling: { maxSize: 3, queueTimeout } };
316-
const pool = connectionPool(options).create({ active: ['foo', 'bar'], expired: [{ isClosing }] });
317-
const isAvailable = td.replace(pool, 'isAvailable');
318-
const update = td.replace(pool, 'update');
319-
const poolConnection = { acquire };
267+
it('queues the request if queueTimeout was not exceeded', done => {
268+
const queueTimeout = 1000;
269+
const options = { pooling: { maxSize: 3, queueTimeout } };
270+
const pool = connectionPool(options).create({ active: ['foo', 'bar', 'baz'] });
271+
const isAvailable = td.replace(pool, 'isAvailable');
272+
const update = td.replace(pool, 'update');
273+
const poolConnection = { acquire };
274+
const now = Date.now();
275+
276+
// The last time it is called, system.time() should return a time
277+
// after queueTimeout.
278+
td.when(system.time()).thenReturn(now + queueTimeout / 10 + 1);
279+
// The first time it is called, system.time() should return the
280+
// initial time.
281+
td.when(system.time(), { times: 1 }).thenReturn(now);
282+
td.when(update()).thenResolve();
283+
td.when(isAvailable()).thenReturn(true);
284+
td.when(connection(options)).thenReturn({ open });
285+
td.when(open()).thenResolve(poolConnection);
286+
287+
// We need to travel in time only after calling
288+
// pool.getConnection(), otherwise, Date.now() will also be in the
289+
// future.
290+
pool.getConnection().then(con => {
291+
expect(td.explain(acquire).callCount).to.equal(1);
292+
expect(pool.activeConnections()).to.deep.include(con);
293+
expect(con).to.deep.equal(poolConnection);
320294

321-
td.when(update()).thenResolve();
322-
td.when(isAvailable()).thenReturn(true);
323-
td.when(isClosing()).thenReturn(true);
324-
td.when(connection(options)).thenReturn({ open });
325-
td.when(open()).thenResolve(poolConnection);
295+
return done();
296+
});
326297

327-
// We need to travel in time only after calling
328-
// pool.getConnection(), otherwise, Date.now() will also be in the
329-
// future.
330-
pool.getConnection().then(con => {
331-
expect(td.explain(acquire).callCount).to.equal(1);
332-
expect(pool.activeConnections()).to.deep.include(con);
333-
expect(con).to.deep.equal(poolConnection);
298+
// We reset the pool after queueTimeout / 10.
299+
setTimeout(() => pool.reset(), queueTimeout / 10);
300+
});
334301

335-
return done();
302+
it('queues the request when an an expired connection is available but is still being closed and queueTimeout was not exceeded', () => {
303+
const queueTimeout = 1000;
304+
const options = { pooling: { maxSize: 3, queueTimeout } };
305+
const pool = connectionPool(options).create({ active: ['foo', 'bar'], expired: [{ isClosing }] });
306+
const isAvailable = td.replace(pool, 'isAvailable');
307+
const update = td.replace(pool, 'update');
308+
const poolConnection = { acquire };
309+
const now = Date.now();
310+
311+
// The last time it is called, system.time() should return a time
312+
// after queueTimeout.
313+
td.when(system.time()).thenReturn(now + queueTimeout / 10 + 1);
314+
// The first time it is called, system.time() should return the
315+
// initial time.
316+
td.when(system.time(), { times: 1 }).thenReturn(now);
317+
td.when(update()).thenResolve();
318+
td.when(isAvailable()).thenReturn(true);
319+
td.when(isClosing()).thenReturn(true);
320+
td.when(connection(options)).thenReturn({ open });
321+
td.when(open()).thenResolve(poolConnection);
322+
323+
// We need to travel in time only after calling
324+
// pool.getConnection(), otherwise, Date.now() will also be in the
325+
// future.
326+
return pool.getConnection()
327+
.then(con => {
328+
expect(td.explain(acquire).callCount).to.equal(1);
329+
expect(pool.activeConnections()).to.deep.include(con);
330+
return expect(con).to.deep.equal(poolConnection);
331+
});
336332
});
337333

338-
tk.travel(new Date(Date.now() + queueTimeout / 10 + 1));
334+
it('fails when queueTimeout is exceeded', () => {
335+
const queueTimeout = 5000;
336+
const pool = connectionPool({ pooling: { maxSize: 3, queueTimeout } }).create({ active: ['foo', 'bar', 'baz'] });
337+
const isAvailable = td.replace(pool, 'isAvailable');
338+
const update = td.replace(pool, 'update');
339+
const now = Date.now();
340+
341+
// The last time it is called, system.time() should return a time
342+
// after queueTimeout.
343+
td.when(system.time()).thenReturn(now + queueTimeout + 1);
344+
// The first time it is called, system.time() should return the
345+
// initial time.
346+
td.when(system.time(), { times: 1 }).thenReturn(now);
347+
td.when(update()).thenResolve();
348+
td.when(isAvailable()).thenReturn(true);
349+
350+
// We need to travel in time only after calling
351+
// pool.getConnection(), otherwise, Date.now() will also be in the
352+
// future.
353+
return pool.getConnection()
354+
.then(() => {
355+
return expect.fail();
356+
})
357+
.catch(err => {
358+
return expect(err.message).to.equal(util.format(errors.MESSAGES.ER_DEVAPI_POOL_QUEUE_TIMEOUT, queueTimeout));
359+
});
360+
});
339361
});
340362

341363
it('does not re-use any connection when the pool is not available', () => {

0 commit comments

Comments
 (0)