diff --git a/.structignore b/.structignore new file mode 100644 index 00000000..f820d242 --- /dev/null +++ b/.structignore @@ -0,0 +1 @@ +test/util/cache-clearer-disabled.spec.js diff --git a/README.md b/README.md index 22577e16..70b2f319 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,13 @@ loaded from `envVarsFile` (if allowed). To allow overwriting of environment variables, prefix the name of the environment variable with `^`. +#### clearCache + +Type: `boolean`
+Default: `true` + +Known accessed caches will be cleared after test has executed when set to `true`. + #### timestamp Type: `number|string`
diff --git a/package.json b/package.json index 90c35f7a..bd991b40 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "joi-strict": "2.0.1", "lodash.clonedeep": "4.5.0", "lodash.get": "4.4.2", + "lru-cache-ext": "3.0.2", "minimist": "1.2.6", "nock": "13.2.4", "object-scan": "18.0.1", diff --git a/src/modules/cache-clearer.js b/src/modules/cache-clearer.js new file mode 100644 index 00000000..79e52d0a --- /dev/null +++ b/src/modules/cache-clearer.js @@ -0,0 +1,57 @@ +import assert from 'assert'; +import LRU from 'lru-cache-ext'; + +const getFns = (obj) => { + const result = []; + const properties = []; + let o = obj; + while (o instanceof Object) { + properties.push(...Object.getOwnPropertyNames(o)); + o = Object.getPrototypeOf(o); + } + for (let i = 0; i < properties.length; i += 1) { + const key = properties[i]; + const value = LRU.prototype[key]; + if (typeof value !== 'function') { + // eslint-disable-next-line no-continue + continue; + } + result.push({ obj, key, value }); + } + return result; +}; + +export default () => { + let injected = false; + const fns = [ + ...getFns(LRU.prototype) + ]; + const caches = []; + + return { + inject: () => { + assert(injected === false); + fns.forEach(({ obj, key, value }) => { + try { + // eslint-disable-next-line no-param-reassign,func-names + obj[key] = function (...args) { + caches.push(this); + return value.call(this, ...args); + }; + } catch (e) { /* ignored */ } + }); + injected = true; + }, + release: () => { + assert(injected === true); + fns.forEach(({ obj, key, value }) => { + try { + // eslint-disable-next-line no-param-reassign + obj[key] = value; + } catch (e) { /* ignored */ } + }); + caches.splice(0).forEach((c) => c.clear()); + injected = false; + } + }; +}; diff --git a/src/util/desc.js b/src/util/desc.js index f6899d44..e3ee3d19 100644 --- a/src/util/desc.js +++ b/src/util/desc.js @@ -12,6 +12,7 @@ import EnvManager from '../modules/env-manager.js'; import TimeKeeper from '../modules/time-keeper.js'; import LogRecorder from '../modules/log-recorder.js'; import RandomSeeder from '../modules/random-seeder.js'; +import CacheClearer from '../modules/cache-clearer.js'; import { getParents, genCassetteName } from './mocha-test.js'; const mocha = { @@ -53,6 +54,7 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { fixtureFolder: Joi.string().optional(), envVarsFile: Joi.string().optional(), envVars: Joi.object().optional().unknown(true).pattern(Joi.string(), Joi.string()), + clearCache: Joi.boolean().optional(), timestamp: Joi.alternatives( Joi.number().integer().min(0), Joi.date().iso() @@ -75,6 +77,7 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { const fixtureFolder = resolve(get(opts, 'fixtureFolder', '$FILENAME__fixtures')); const envVarsFile = resolve(get(opts, 'envVarsFile', '$FILENAME.env.yml')); const envVars = get(opts, 'envVars', null); + const clearCache = get(opts, 'clearCache', true); const timestamp = get(opts, 'timestamp', null); const record = get(opts, 'record', false); const cryptoSeed = get(opts, 'cryptoSeed', null); @@ -84,6 +87,7 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { let dir = null; let requestRecorder = null; + let cacheClearer = null; let envManagerFile = null; let envManagerDesc = null; let timeKeeper = null; @@ -130,6 +134,9 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { // eslint-disable-next-line func-names mocha.before(function () { return (async () => { + if (clearCache !== false) { + cacheClearer = CacheClearer(); + } if (getParents(this.test).length === 3 && fs.existsSync(envVarsFile)) { envManagerFile = EnvManager({ envVars: fs.smartRead(envVarsFile), allowOverwrite: false }); envManagerFile.apply(); @@ -190,6 +197,9 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { // eslint-disable-next-line func-names mocha.beforeEach(function () { return (async () => { + if (cacheClearer !== null) { + cacheClearer.inject(); + } if (useTmpDir === true) { tmp.setGracefulCleanup(); dir = tmp.dirSync({ keep: false, unsafeCleanup: true }).name; @@ -222,6 +232,9 @@ const desc = (suiteName, optsOrTests, testsOrNull = null) => { if (dir !== null) { dir = null; } + if (cacheClearer !== null) { + cacheClearer.release(); + } })(); }); diff --git a/test/util/cache-clearer-disabled.spec.js b/test/util/cache-clearer-disabled.spec.js new file mode 100644 index 00000000..ab8d9a53 --- /dev/null +++ b/test/util/cache-clearer-disabled.spec.js @@ -0,0 +1,19 @@ +import LRU from 'lru-cache-ext'; +import { expect } from 'chai'; +import describe from '../../src/util/desc.js'; + +describe('Testing Clear Cache Disabled', { clearCache: false }, () => { + let cache; + before(() => { + cache = new LRU({ ttl: 100, max: 100 }); + }); + + it('First', () => { + expect(cache.has('a')).to.equal(false); + cache.set('a', 1); + }); + + it('Second', () => { + expect(cache.has('a')).to.equal(true); + }); +}); diff --git a/test/util/desc.spec.js b/test/util/desc.spec.js index 230b685a..60be80f1 100644 --- a/test/util/desc.spec.js +++ b/test/util/desc.spec.js @@ -5,6 +5,7 @@ import path from 'path'; import axios from 'axios'; import fancyLog from 'fancy-log'; import { expect } from 'chai'; +import LRU from 'lru-cache-ext'; import describe from '../../src/util/desc.js'; const dirPrefix = path.join(os.tmpdir(), 'tmp-'); @@ -261,4 +262,21 @@ describe('Testing { describe }', () => { state.push('testTwo'); }); }); + + describe('Testing Clear Cache', () => { + let cache; + before(() => { + cache = new LRU({ ttl: 100, max: 100 }); + }); + + it('First', () => { + expect(cache.has('a')).to.equal(false); + cache.set('a', 1); + }); + + it('Second', () => { + expect(cache.has('a')).to.equal(false); + cache.set('a', 1); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 190fc7df..b677790e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2734,6 +2734,18 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lru-cache-ext@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lru-cache-ext/-/lru-cache-ext-3.0.2.tgz#5dc1fb94bc22431c8a8fe602226155838d940b52" + integrity sha512-PQ0kInomO2I3HZuiagNTkaNyiDQkcIyeEahHRWSBVsEaxPqXb4FTPU4ajl9znX+C5antzQ0UWcfWrxy8kAhUrw== + dependencies: + lru-cache "7.8.1" + +lru-cache@7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb" + integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== + lru-cache@^4.0.0, lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"