Skip to content

Commit d78e324

Browse files
e11syneSpecc
andauthored
fix(grouper): patterns grouping (#475)
* chore(tests): fix test speed * fix(grouper): fix event grouping by pattern * chore(): cover grouping with tests * chore(): fix grouper tests * chore() : eslint fix * imp(): memoization util * chore(): imp test description * chore(): remove redundant case --------- Co-authored-by: Peter <[email protected]>
1 parent 2a61c10 commit d78e324

File tree

7 files changed

+269
-57
lines changed

7 files changed

+269
-57
lines changed

.env.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ REPORT_NOTIFY_URL=http://mock.com/
3838

3939
# Url for connecting to Redis
4040
REDIS_URL=redis://localhost:6379
41+
42+
# Disable memoization in tests
43+
MEMOIZATION_TTL=-1

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ module.exports = {
2828
setupFiles: [ './jest.setup.js' ],
2929

3030
setupFilesAfterEnv: ['./jest.setup.redis-mock.js', './jest.setup.mongo-repl-set.js'],
31-
31+
3232
globalTeardown: './jest.global-teardown.js',
3333
};

jest.global-teardown.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ module.exports = () => {
66
process.exit(0);
77
}, 1000);
88
}
9-
}
9+
};

lib/memoize/index.test.ts

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('memoize decorator — per-test inline classes', () => {
2626
@memoize({ strategy: 'concat', ttl: 60_000, max: 50 })
2727
public async run(a: number, b: string) {
2828
this.calls += 1;
29+
2930
return `${a}-${b}`;
3031
}
3132
}
@@ -37,7 +38,7 @@ describe('memoize decorator — per-test inline classes', () => {
3738
*/
3839
expect(await sample.run(1, 'x')).toBe('1-x');
3940
/**
40-
* In this case
41+
* In this case
4142
*/
4243
expect(await sample.run(1, 'x')).toBe('1-x');
4344
expect(await sample.run(1, 'x')).toBe('1-x');
@@ -52,6 +53,7 @@ describe('memoize decorator — per-test inline classes', () => {
5253
@memoize({ strategy: 'concat' })
5354
public async run(a: unknown, b: unknown) {
5455
this.calls += 1;
56+
5557
return `${String(a)}|${String(b)}`;
5658
}
5759
}
@@ -84,9 +86,11 @@ describe('memoize decorator — per-test inline classes', () => {
8486
it('should memoize return value for stringified objects across several calls', async () => {
8587
class Sample {
8688
public calls = 0;
89+
8790
@memoize({ strategy: 'concat' })
8891
public async run(x: unknown, y: unknown) {
8992
this.calls += 1;
93+
9094
return 'ok';
9195
}
9296
}
@@ -103,9 +107,11 @@ describe('memoize decorator — per-test inline classes', () => {
103107
it('should memoize return value for method with non-default arguments (NaN, Infinity, -0, Symbol, Date, RegExp) still cache same-args', async () => {
104108
class Sample {
105109
public calls = 0;
110+
106111
@memoize({ strategy: 'concat' })
107112
public async run(...args: unknown[]) {
108113
this.calls += 1;
114+
109115
return args.map(String).join(',');
110116
}
111117
}
@@ -127,27 +133,31 @@ describe('memoize decorator — per-test inline classes', () => {
127133

128134
class Sample {
129135
public calls = 0;
136+
130137
@memoize({ strategy: 'hash' })
131138
public async run(...args: unknown[]) {
132139
this.calls += 1;
140+
133141
return 'ok';
134142
}
135143
}
136144
const sample = new Sample();
137145

138-
await sample.run({a: 1}, undefined, 0);
139-
await sample.run({a: 1}, undefined, 0);
146+
await sample.run({ a: 1 }, undefined, 0);
147+
await sample.run({ a: 1 }, undefined, 0);
140148

141-
expect(hashSpy).toHaveBeenCalledWith([{a: 1}, undefined, 0], 'blake2b512', 'base64url');
149+
expect(hashSpy).toHaveBeenCalledWith([ { a: 1 }, undefined, 0], 'blake2b512', 'base64url');
142150
expect(sample.calls).toBe(1);
143151
});
144152

145153
it('should not memoize return value with hash strategy and different arguments', async () => {
146154
class Sample {
147155
public calls = 0;
156+
148157
@memoize({ strategy: 'hash' })
149158
public async run(...args: unknown[]) {
150159
this.calls += 1;
160+
151161
return 'ok';
152162
}
153163
}
@@ -163,9 +173,11 @@ describe('memoize decorator — per-test inline classes', () => {
163173
it('should memoize return value with hash strategy across several calls with same args', async () => {
164174
class Sample {
165175
public calls = 0;
176+
166177
@memoize({ strategy: 'hash' })
167178
public async run(arg: unknown) {
168179
this.calls += 1;
180+
169181
return 'ok';
170182
}
171183
}
@@ -186,9 +198,11 @@ describe('memoize decorator — per-test inline classes', () => {
186198

187199
class Sample {
188200
public calls = 0;
201+
189202
@memoizeWithMockedTimers({ strategy: 'concat', ttl: 1_000 })
190203
public async run(x: string) {
191204
this.calls += 1;
205+
192206
return x;
193207
}
194208
}
@@ -204,16 +218,19 @@ describe('memoize decorator — per-test inline classes', () => {
204218

205219
await sample.run('k1');
206220
expect(sample.calls).toBe(2);
207-
208221
});
209222

210223
it('error calls should never be momized', async () => {
211224
class Sample {
212225
public calls = 0;
226+
213227
@memoize()
214228
public async run(x: number) {
215229
this.calls += 1;
216-
if (x === 1) throw new Error('boom');
230+
if (x === 1) {
231+
throw new Error('boom');
232+
}
233+
217234
return x * 2;
218235
}
219236
}
@@ -226,4 +243,87 @@ describe('memoize decorator — per-test inline classes', () => {
226243
await expect(sample.run(1)).rejects.toThrow('boom');
227244
expect(sample.calls).toBe(2);
228245
});
246+
247+
it('should NOT cache results listed in skipCache (primitives)', async () => {
248+
class Sample {
249+
public calls = 0;
250+
251+
@memoize({ strategy: 'concat', skipCache: [null, undefined, 0, false, ''] })
252+
public async run(kind: 'null' | 'undef' | 'zero' | 'false' | 'empty') {
253+
this.calls += 1;
254+
switch (kind) {
255+
case 'null': return null;
256+
case 'undef': return undefined;
257+
case 'zero': return 0;
258+
case 'false': return false;
259+
case 'empty': return '';
260+
}
261+
}
262+
}
263+
264+
const sample = new Sample();
265+
266+
// Each repeated call should invoke the original again because result is in skipCache.
267+
await sample.run('null');
268+
await sample.run('null');
269+
270+
await sample.run('undef');
271+
await sample.run('undef');
272+
273+
await sample.run('zero');
274+
await sample.run('zero');
275+
276+
await sample.run('false');
277+
await sample.run('false');
278+
279+
await sample.run('empty');
280+
await sample.run('empty');
281+
282+
// 5 kinds × 2 calls each = 10 calls, none cached
283+
expect(sample.calls).toBe(10);
284+
});
285+
286+
it('should cache results NOT listed in skipCache', async () => {
287+
class Sample {
288+
public calls = 0;
289+
290+
@memoize({ strategy: 'concat', skipCache: [null, undefined] })
291+
public async run(x: number) {
292+
this.calls += 1;
293+
// returns a non-skipped primitive
294+
return x * 2;
295+
}
296+
}
297+
298+
const sample = new Sample();
299+
300+
expect(await sample.run(21)).toBe(42);
301+
expect(await sample.run(21)).toBe(42);
302+
303+
expect(sample.calls).toBe(1);
304+
});
305+
306+
it('should use equality for skipCache with objects: deep equal objects are cached', async () => {
307+
const deepEqualObject = { a: 1 };
308+
309+
class Sample {
310+
public calls = 0;
311+
312+
@memoize({ strategy: 'concat', skipCache: [deepEqualObject] })
313+
public async run() {
314+
this.calls += 1;
315+
316+
return { a: 1 };
317+
}
318+
}
319+
320+
const sample = new Sample();
321+
322+
const first = await sample.run();
323+
const second = await sample.run();
324+
325+
expect(first).toEqual({ a: 1 });
326+
expect(second).toBe(first);
327+
expect(sample.calls).toBe(1);
328+
});
229329
});

lib/memoize/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ export interface MemoizeOptions {
2626
* Strategy for key generation
2727
*/
2828
strategy?: MemoizeKeyStrategy;
29+
30+
/**
31+
* It allows to skip caching for list of return values specified
32+
*/
33+
skipCache?: any[]
2934
}
3035

3136
/**
@@ -40,6 +45,7 @@ export function memoize(options: MemoizeOptions = {}): MethodDecorator {
4045
max = 50,
4146
ttl = 1000 * 60 * 30,
4247
strategy = 'concat',
48+
skipCache = []
4349
} = options;
4450
/* eslint-enable */
4551

@@ -84,7 +90,9 @@ export function memoize(options: MemoizeOptions = {}): MethodDecorator {
8490
try {
8591
const result = await originalMethod.apply(this, args);
8692

87-
cache.set(key, result);
93+
if (!skipCache.includes(result)) {
94+
cache.set(key, result);
95+
}
8896

8997
return result;
9098
} catch (err) {

0 commit comments

Comments
 (0)