From 011bfbb236d575440485bfc6725146965eb2e9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 7 Oct 2016 16:26:18 +0200 Subject: [PATCH] test/kvao: add connectorCapabilities options - canExpire - canQueryTtl - ttlPrecision - canIterateKeys - canIterateLargeKeySets These options allow connectors to disable shared tests for features that are not supported/implemented. --- test/helpers/bdd-if.js | 19 +++++++++++++++++++ test/kvao.suite.js | 7 +++++++ test/kvao/expire.suite.js | 23 +++++++++++++++-------- test/kvao/get-set.suite.js | 24 +++++++++++++----------- test/kvao/iterate-keys.suite.js | 5 ++++- test/kvao/keys.suite.js | 8 ++++++-- test/kvao/ttl.suite.js | 29 ++++++++++++++++++++++------- 7 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 test/helpers/bdd-if.js diff --git a/test/helpers/bdd-if.js b/test/helpers/bdd-if.js new file mode 100644 index 00000000..8c120091 --- /dev/null +++ b/test/helpers/bdd-if.js @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +'use strict'; + +exports.describeIf = function describeIf(cond, name, fn) { + if (cond) + describe(name, fn); + else + describe.skip(name, fn); +}; + +exports.itIf = function itIf(cond, name, fn) { + if (cond) + it(name, fn); + else + it.skip(name, fn); +}; diff --git a/test/kvao.suite.js b/test/kvao.suite.js index 77bc886c..a464d7a0 100644 --- a/test/kvao.suite.js +++ b/test/kvao.suite.js @@ -1,10 +1,17 @@ 'use strict'; var debug = require('debug')('test'); +var extend = require('util')._extend; var fs = require('fs'); var path = require('path'); module.exports = function(dataSourceFactory, connectorCapabilities) { + connectorCapabilities = extend({ + // Even when the backend supports millisecond precision, + // it's better to use intervals at least 10ms long in the tests + ttlPrecision: 10, + }, connectorCapabilities); + describe('KeyValue API', function loadAllTestFiles() { var testRoot = path.resolve(__dirname, 'kvao'); var testFiles = fs.readdirSync(testRoot); diff --git a/test/kvao/expire.suite.js b/test/kvao/expire.suite.js index 9bb2f800..63784e0f 100644 --- a/test/kvao/expire.suite.js +++ b/test/kvao/expire.suite.js @@ -1,11 +1,18 @@ 'use strict'; +var bdd = require('../helpers/bdd-if'); var should = require('should'); var helpers = require('./_helpers'); var Promise = require('bluebird'); module.exports = function(dataSourceFactory, connectorCapabilities) { - describe('expire', function() { + // While we support millisecond precision, for the purpose of tests + // it's better to use intervals at least 10ms long. + var ttlPrecision = connectorCapabilities.ttlPrecision || 10; + + var canExpire = connectorCapabilities.canExpire !== false; + + bdd.describeIf(canExpire, 'expire', function() { var CacheItem; beforeEach(function unpackContext() { CacheItem = helpers.givenCacheItem(dataSourceFactory); @@ -14,7 +21,7 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { it('sets key ttl - Callback API', function(done) { CacheItem.set('a-key', 'a-value', function(err) { if (err) return done(err); - CacheItem.expire('a-key', 1, function(err) { + CacheItem.expire('a-key', ttlPrecision, function(err) { if (err) return done(err); setTimeout(function() { CacheItem.get('a-key', function(err, value) { @@ -22,22 +29,22 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { should.equal(value, null); done(); }); - }, 20); + }, 2 * ttlPrecision); }); }); }); it('sets key ttl - Promise API', function() { return CacheItem.set('a-key', 'a-value') - .then(function() { return CacheItem.expire('a-key', 1); }) - .delay(20) + .then(function() { return CacheItem.expire('a-key', ttlPrecision); }) + .delay(2 * ttlPrecision) .then(function() { return CacheItem.get('a-key'); }) .then(function(value) { should.equal(value, null); }); }); it('returns error when expiring a key that has expired', function() { - return Promise.resolve(CacheItem.set('expired-key', 'a-value', 1)) - .delay(20) + return Promise.resolve(CacheItem.set('expired-key', 'a-value', ttlPrecision)) + .delay(2 * ttlPrecision) .then(function() { return CacheItem.expire('expired-key', 1000); }) .then( function() { throw new Error('expire() should have failed'); }, @@ -48,7 +55,7 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { }); it('returns error when key does not exist', function() { - return CacheItem.expire('key-does-not-exist', 1).then( + return CacheItem.expire('key-does-not-exist', ttlPrecision).then( function() { throw new Error('expire() should have failed'); }, function(err) { err.message.should.match(/key-does-not-exist/); diff --git a/test/kvao/get-set.suite.js b/test/kvao/get-set.suite.js index 3405fc89..89960c7b 100644 --- a/test/kvao/get-set.suite.js +++ b/test/kvao/get-set.suite.js @@ -5,6 +5,8 @@ var helpers = require('./_helpers'); var Promise = require('bluebird'); module.exports = function(dataSourceFactory, connectorCapabilities) { + var TTL_PRECISION = connectorCapabilities.ttlPrecision; + describe('get/set', function() { var CacheItem; beforeEach(function unpackContext() { @@ -68,8 +70,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { }); it('honours options.ttl', function() { - return CacheItem.set('a-key', 'a-value', {ttl: 10}) - .delay(20) + return CacheItem.set('a-key', 'a-value', {ttl: TTL_PRECISION}) + .delay(2 * TTL_PRECISION) .then(function() { return CacheItem.get('a-key'); }) .then(function(value) { should.equal(value, null); }); }); @@ -79,22 +81,22 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { return CacheItem.get('key-does-not-exist') .then(function(value) { should.equal(value, null); }); }); - - it('converts numeric options arg to options.ttl', function() { - return CacheItem.set('a-key', 'a-value', 10) - .delay(20) - .then(function() { return CacheItem.get('a-key'); }) - .then(function(value) { should.equal(value, null); }); - }); }); describe('set', function() { + it('converts numeric options arg to options.ttl', function() { + return CacheItem.set('a-key', 'a-value', TTL_PRECISION) + .delay(2 * TTL_PRECISION) + .then(function() { return CacheItem.get('a-key'); }) + .then(function(value) { should.equal(value, null); }); + }); + it('resets TTL timer', function() { - return CacheItem.set('a-key', 'a-value', {ttl: 10}) + return CacheItem.set('a-key', 'a-value', {ttl: TTL_PRECISION}) .then(function() { return CacheItem.set('a-key', 'another-value'); // no TTL }) - .delay(20) + .delay(2 * TTL_PRECISION) .then(function() { return CacheItem.get('a-key'); }) .then(function(value) { should.equal(value, 'another-value'); }); }); diff --git a/test/kvao/iterate-keys.suite.js b/test/kvao/iterate-keys.suite.js index 4036e3fa..8e4ec0a9 100644 --- a/test/kvao/iterate-keys.suite.js +++ b/test/kvao/iterate-keys.suite.js @@ -1,13 +1,16 @@ 'use strict'; var asyncIterators = require('async-iterators'); +var bdd = require('../helpers/bdd-if'); var helpers = require('./_helpers'); var Promise = require('bluebird'); var should = require('should'); var toArray = Promise.promisify(asyncIterators.toArray); module.exports = function(dataSourceFactory, connectorCapabilities) { - describe('iterateKeys', function() { + var canIterateKeys = connectorCapabilities.canIterateKeys !== false; + + bdd.describeIf(canIterateKeys, 'iterateKeys', function() { var CacheItem; beforeEach(function unpackContext() { CacheItem = helpers.givenCacheItem(dataSourceFactory); diff --git a/test/kvao/keys.suite.js b/test/kvao/keys.suite.js index 1817b1fa..a571ae87 100644 --- a/test/kvao/keys.suite.js +++ b/test/kvao/keys.suite.js @@ -1,11 +1,14 @@ 'use strict'; +var bdd = require('../helpers/bdd-if'); var helpers = require('./_helpers'); var Promise = require('bluebird'); var should = require('should'); module.exports = function(dataSourceFactory, connectorCapabilities) { - describe('keys', function() { + var canIterateKeys = connectorCapabilities.canIterateKeys !== false; + + bdd.describeIf(canIterateKeys, 'keys', function() { var CacheItem; beforeEach(function unpackContext() { CacheItem = helpers.givenCacheItem(dataSourceFactory); @@ -54,7 +57,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { }); }); - it('handles large key set', function() { + var largeKeySets = connectorCapabilities.canIterateLargeKeySets !== false; + bdd.itIf(largeKeySets, 'handles large key set', function() { var expectedKeys = []; for (var ix = 0; ix < 1000; ix++) expectedKeys.push('key-' + ix); diff --git a/test/kvao/ttl.suite.js b/test/kvao/ttl.suite.js index ea5e2231..e772c98a 100644 --- a/test/kvao/ttl.suite.js +++ b/test/kvao/ttl.suite.js @@ -1,11 +1,25 @@ 'use strict'; +var bdd = require('../helpers/bdd-if'); var should = require('should'); var helpers = require('./_helpers'); var Promise = require('bluebird'); module.exports = function(dataSourceFactory, connectorCapabilities) { - describe('ttl', function() { + var TTL_PRECISION = connectorCapabilities.ttlPrecision; + + // Use ~1s for stores with precision of 1 ms, + // about 3s for stores with precision of 1s. + var INITIAL_TTL = Math.max(TTL_PRECISION + 1000, TTL_PRECISION * 3); + + // A small delay to allow the backend to process the request, run any + // TTL/expire checks, etc. Use 1ms for backends supporting sub-10ms + // resolution to ensure the delay is not too short.. + var SMALL_DELAY = Math.max(1, Math.floor(TTL_PRECISION / 10)); + + var canQueryTtl = connectorCapabilities.canQueryTtl !== false; + + bdd.describeIf(canQueryTtl, 'ttl', function() { var CacheItem; beforeEach(function unpackContext() { CacheItem = helpers.givenCacheItem(dataSourceFactory); @@ -14,19 +28,19 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { it('gets TTL when key with unexpired TTL exists - Promise API', function() { return Promise.resolve( - CacheItem.set('a-key', 'a-value', {ttl: 1000})) - .delay(1) + CacheItem.set('a-key', 'a-value', {ttl: INITIAL_TTL})) + .delay(SMALL_DELAY) .then(function() { return CacheItem.ttl('a-key'); }) - .then(function(ttl) { ttl.should.be.within(1, 1000); }); + .then(function(ttl) { ttl.should.be.within(1, INITIAL_TTL); }); }); it('gets TTL when key with unexpired TTL exists - Callback API', function(done) { - CacheItem.set('a-key', 'a-value', {ttl: 1000}, function(err) { + CacheItem.set('a-key', 'a-value', {ttl: INITIAL_TTL}, function(err) { if (err) return done(err); CacheItem.ttl('a-key', function(err, ttl) { if (err) return done(err); - ttl.should.be.within(1, 1000); + ttl.should.be.within(1, INITIAL_TTL); done(); }); }); @@ -40,7 +54,8 @@ module.exports = function(dataSourceFactory, connectorCapabilities) { it('fails when getting TTL for a key with expired TTL', function() { return Promise.resolve( - CacheItem.set('expired-key', 'a-value', {ttl: 10})).delay(20) + CacheItem.set('expired-key', 'a-value', {ttl: TTL_PRECISION})) + .delay(2 * TTL_PRECISION) .then(function() { return CacheItem.ttl('expired-key'); })