ueberdb2 4.1.1 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/.eslintignore +0 -2
- package/.eslintrc.cjs +0 -66
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/npmpublish.yml +0 -134
- package/.travis.yml +0 -46
- package/CHANGELOG.md +0 -304
- package/CONTRIBUTING.md +0 -103
- package/SECURITY.md +0 -5
- package/databases/cassandra_db.ts +0 -265
- package/databases/couch_db.ts +0 -189
- package/databases/dirty_db.ts +0 -85
- package/databases/dirty_git_db.ts +0 -82
- package/databases/elasticsearch_db.ts +0 -257
- package/databases/memory_db.ts +0 -41
- package/databases/mock_db.ts +0 -43
- package/databases/mongodb_db.ts +0 -142
- package/databases/mssql_db.ts +0 -226
- package/databases/mysql_db.ts +0 -183
- package/databases/postgres_db.ts +0 -213
- package/databases/postgrespool_db.ts +0 -11
- package/databases/redis_db.ts +0 -129
- package/databases/rethink_db.ts +0 -114
- package/databases/sqlite_db.ts +0 -159
- package/docker-compose.yml +0 -44
- package/index.ts +0 -224
- package/lib/AbstractDatabase.ts +0 -79
- package/lib/CacheAndBufferLayer.ts +0 -665
- package/lib/logging.ts +0 -33
- package/test/lib/databases.ts +0 -73
- package/test/lib/mysql.sql +0 -84
- package/test/test.ts +0 -328
- package/test/test_bulk.ts +0 -69
- package/test/test_elasticsearch.ts +0 -128
- package/test/test_findKeys.ts +0 -41
- package/test/test_flush.ts +0 -55
- package/test/test_getSub.ts +0 -28
- package/test/test_lru.ts +0 -151
- package/test/test_memory.ts +0 -32
- package/test/test_metrics.ts +0 -734
- package/test/test_mysql.ts +0 -62
- package/test/test_postgres.ts +0 -16
- package/test/test_setSub.ts +0 -19
- package/test/test_tojson.ts +0 -34
package/test/test_metrics.ts
DELETED
|
@@ -1,734 +0,0 @@
|
|
|
1
|
-
import assert$0 from 'assert';
|
|
2
|
-
import * as ueberdb from '../index';
|
|
3
|
-
'use strict';
|
|
4
|
-
const assert = assert$0.strict;
|
|
5
|
-
// Gate is a normal Promise that resolves when its open() method is called.
|
|
6
|
-
// @ts-expect-error TS(2508): No base constructor has the specified number of ty... Remove this comment to see the full error message
|
|
7
|
-
class Gate extends Promise {
|
|
8
|
-
open: any;
|
|
9
|
-
constructor(executor = null) {
|
|
10
|
-
let open;
|
|
11
|
-
super((resolve: any, reject: any) => {
|
|
12
|
-
open = resolve;
|
|
13
|
-
if (executor != null)
|
|
14
|
-
// @ts-expect-error TS(2349): This expression is not callable.
|
|
15
|
-
{ executor(resolve, reject); }
|
|
16
|
-
});
|
|
17
|
-
this.open = open;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const diffMetrics = (before: any, after: any) => {
|
|
21
|
-
const diff = {};
|
|
22
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
23
|
-
assert.equal(Object.keys(before).length, Object.keys(after).length);
|
|
24
|
-
for (const [k, bv] of Object.entries(before)) {
|
|
25
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
26
|
-
assert(bv != null);
|
|
27
|
-
const av = after[k];
|
|
28
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
29
|
-
assert(av != null);
|
|
30
|
-
// @ts-expect-error TS(2571): Object is of type 'unknown'.
|
|
31
|
-
if (av - bv > 0)
|
|
32
|
-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
33
|
-
{ diff[k] = av - bv; }
|
|
34
|
-
}
|
|
35
|
-
return diff;
|
|
36
|
-
};
|
|
37
|
-
const assertMetricsDelta = (before: any, after: any, wantDelta: any) => {
|
|
38
|
-
wantDelta = {...wantDelta};
|
|
39
|
-
for (const [k, v] of Object.entries(wantDelta)) {
|
|
40
|
-
if (v === 0) delete wantDelta[k];
|
|
41
|
-
}
|
|
42
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
43
|
-
assert.deepEqual(diffMetrics(before, after), wantDelta);
|
|
44
|
-
};
|
|
45
|
-
describe(__filename, () => {
|
|
46
|
-
let db: any;
|
|
47
|
-
let key: any;
|
|
48
|
-
let mock: any;
|
|
49
|
-
before(async () => {
|
|
50
|
-
const settings = {};
|
|
51
|
-
db = new ueberdb.Database('mock', settings);
|
|
52
|
-
// @ts-expect-error TS(2339): Property 'mock' does not exist on type '{}'.
|
|
53
|
-
mock = settings.mock;
|
|
54
|
-
mock.once('init', (cb: any) => cb());
|
|
55
|
-
await db.init();
|
|
56
|
-
});
|
|
57
|
-
after(async () => {
|
|
58
|
-
mock.once('close', (cb: any) => cb());
|
|
59
|
-
await db.close();
|
|
60
|
-
});
|
|
61
|
-
beforeEach(async function (this: any) {
|
|
62
|
-
key = this.currentTest.fullTitle(); // Use test title to avoid collisions with other tests.
|
|
63
|
-
});
|
|
64
|
-
afterEach(async () => {
|
|
65
|
-
mock.removeAllListeners();
|
|
66
|
-
});
|
|
67
|
-
describe('reads', () => {
|
|
68
|
-
const tcs = [
|
|
69
|
-
{name: 'get', f: (key: any) => db.get(key)},
|
|
70
|
-
{name: 'getSub', f: (key: any) => db.getSub(key, ['s'])},
|
|
71
|
-
];
|
|
72
|
-
for (const tc of tcs) {
|
|
73
|
-
describe(tc.name, () => {
|
|
74
|
-
const subtcs = [
|
|
75
|
-
{
|
|
76
|
-
name: 'cache miss',
|
|
77
|
-
val: '{"s": "v"}',
|
|
78
|
-
wantMetrics: {
|
|
79
|
-
lockReleases: 1,
|
|
80
|
-
readsFinished: 1,
|
|
81
|
-
readsFromDbFinished: 1,
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'cache hit',
|
|
86
|
-
cacheHit: true,
|
|
87
|
-
val: '{"s": "v"}',
|
|
88
|
-
wantMetrics: {
|
|
89
|
-
lockAcquires: 1,
|
|
90
|
-
lockReleases: 1,
|
|
91
|
-
reads: 1,
|
|
92
|
-
readsFinished: 1,
|
|
93
|
-
readsFromCache: 1,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'read error',
|
|
98
|
-
err: new Error('test'),
|
|
99
|
-
wantMetrics: {
|
|
100
|
-
lockReleases: 1,
|
|
101
|
-
readsFailed: 1,
|
|
102
|
-
readsFinished: 1,
|
|
103
|
-
readsFromDbFailed: 1,
|
|
104
|
-
readsFromDbFinished: 1,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: 'json error',
|
|
109
|
-
val: 'ignore me -- this is intentionally invalid json',
|
|
110
|
-
wantJsonErr: true,
|
|
111
|
-
wantMetrics: {
|
|
112
|
-
lockReleases: 1,
|
|
113
|
-
readsFailed: 1,
|
|
114
|
-
readsFinished: 1,
|
|
115
|
-
readsFromDbFinished: 1,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
];
|
|
119
|
-
for (const subtc of subtcs) {
|
|
120
|
-
it(subtc.name, async () => {
|
|
121
|
-
if (subtc.cacheHit) {
|
|
122
|
-
mock.once('get', (key: any, cb: any) => { cb(null, subtc.val); });
|
|
123
|
-
await tc.f(key);
|
|
124
|
-
}
|
|
125
|
-
let finishDbRead;
|
|
126
|
-
const dbReadStarted = new Promise((resolve) => {
|
|
127
|
-
mock.once('get', (key: any, cb: any) => {
|
|
128
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
129
|
-
assert(!subtc.cacheHit, 'value should have been cached');
|
|
130
|
-
// @ts-expect-error TS(2794): Expected 1 arguments, but got 0. Did you forget to... Remove this comment to see the full error message
|
|
131
|
-
resolve();
|
|
132
|
-
new Promise((resolve) => { finishDbRead = resolve; })
|
|
133
|
-
.then(() => cb(subtc.err, subtc.val));
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
let before = {...db.metrics};
|
|
137
|
-
let readFinished = tc.f(key);
|
|
138
|
-
if (!subtc.cacheHit) {
|
|
139
|
-
await dbReadStarted;
|
|
140
|
-
assertMetricsDelta(before, db.metrics, {
|
|
141
|
-
lockAcquires: 1,
|
|
142
|
-
reads: 1,
|
|
143
|
-
readsFromDb: 1,
|
|
144
|
-
});
|
|
145
|
-
before = {...db.metrics};
|
|
146
|
-
// @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
|
147
|
-
finishDbRead();
|
|
148
|
-
}
|
|
149
|
-
if (subtc.err) readFinished = assert.rejects(readFinished, subtc.err);
|
|
150
|
-
if (subtc.wantJsonErr) readFinished = assert.rejects(readFinished, {message: /JSON/});
|
|
151
|
-
await readFinished;
|
|
152
|
-
assertMetricsDelta(before, db.metrics, subtc.wantMetrics);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
it('read of in-progress write', async () => {
|
|
156
|
-
let finishWrite;
|
|
157
|
-
const writeStarted = new Promise((resolve) => {
|
|
158
|
-
mock.once('set', (key: any, val: any, cb: any) => {
|
|
159
|
-
// @ts-expect-error TS(2794): Expected 1 arguments, but got 0. Did you forget to... Remove this comment to see the full error message
|
|
160
|
-
resolve();
|
|
161
|
-
new Promise((resolve) => { finishWrite = resolve; }).then(() => cb());
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
const writeFinished = db.set(key, {s: 'v'});
|
|
165
|
-
const flushed = db.flush(); // Speed up the tests.
|
|
166
|
-
await writeStarted;
|
|
167
|
-
mock.once('get', (key: any, cb: any) => { assert.fail('value should be cached'); });
|
|
168
|
-
const before = {...db.metrics};
|
|
169
|
-
await tc.f(key);
|
|
170
|
-
assertMetricsDelta(before, db.metrics, {
|
|
171
|
-
lockAcquires: 1,
|
|
172
|
-
lockReleases: 1,
|
|
173
|
-
reads: 1,
|
|
174
|
-
readsFinished: 1,
|
|
175
|
-
readsFromCache: 1,
|
|
176
|
-
});
|
|
177
|
-
// @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
|
178
|
-
finishWrite();
|
|
179
|
-
await writeFinished;
|
|
180
|
-
await flushed;
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
describe('writes', () => {
|
|
186
|
-
const tcs = [
|
|
187
|
-
{
|
|
188
|
-
name: 'remove ok',
|
|
189
|
-
action: async () => await db.remove(key),
|
|
190
|
-
wantOps: [
|
|
191
|
-
{
|
|
192
|
-
wantFns: ['remove'],
|
|
193
|
-
wantMetricsDelta: {
|
|
194
|
-
lockAcquires: 1,
|
|
195
|
-
lockReleases: 1,
|
|
196
|
-
writes: 1,
|
|
197
|
-
writesToDb: 1,
|
|
198
|
-
},
|
|
199
|
-
cbArgs: [[null]],
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
wantErr: null,
|
|
203
|
-
wantMetricsDelta: {
|
|
204
|
-
writesFinished: 1,
|
|
205
|
-
writesToDbFinished: 1,
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
name: 'remove error',
|
|
210
|
-
action: async () => await db.remove(key),
|
|
211
|
-
wantOps: [
|
|
212
|
-
{
|
|
213
|
-
wantFns: ['remove'],
|
|
214
|
-
wantMetricsDelta: {
|
|
215
|
-
lockAcquires: 1,
|
|
216
|
-
lockReleases: 1,
|
|
217
|
-
writes: 1,
|
|
218
|
-
writesToDb: 1,
|
|
219
|
-
},
|
|
220
|
-
cbArgs: [[new Error('test')]],
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
wantErr: {message: 'test'},
|
|
224
|
-
wantMetricsDelta: {
|
|
225
|
-
writesFailed: 1,
|
|
226
|
-
writesFinished: 1,
|
|
227
|
-
writesToDbFailed: 1,
|
|
228
|
-
writesToDbFinished: 1,
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
name: 'set ok',
|
|
233
|
-
action: async () => await db.set(key, 'v'),
|
|
234
|
-
wantOps: [
|
|
235
|
-
{
|
|
236
|
-
wantFns: ['set'],
|
|
237
|
-
wantMetricsDelta: {
|
|
238
|
-
lockAcquires: 1,
|
|
239
|
-
lockReleases: 1,
|
|
240
|
-
writes: 1,
|
|
241
|
-
writesToDb: 1,
|
|
242
|
-
},
|
|
243
|
-
cbArgs: [[null]],
|
|
244
|
-
},
|
|
245
|
-
],
|
|
246
|
-
wantErr: null,
|
|
247
|
-
wantMetricsDelta: {
|
|
248
|
-
writesFinished: 1,
|
|
249
|
-
writesToDbFinished: 1,
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: 'set db error',
|
|
254
|
-
action: async () => await db.set(key, 'v'),
|
|
255
|
-
wantOps: [
|
|
256
|
-
{
|
|
257
|
-
wantFns: ['set'],
|
|
258
|
-
wantMetricsDelta: {
|
|
259
|
-
lockAcquires: 1,
|
|
260
|
-
lockReleases: 1,
|
|
261
|
-
writes: 1,
|
|
262
|
-
writesToDb: 1,
|
|
263
|
-
},
|
|
264
|
-
cbArgs: [[new Error('test')]],
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
wantErr: {message: 'test'},
|
|
268
|
-
wantMetricsDelta: {
|
|
269
|
-
writesFailed: 1,
|
|
270
|
-
writesFinished: 1,
|
|
271
|
-
writesToDbFailed: 1,
|
|
272
|
-
writesToDbFinished: 1,
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: 'set json error',
|
|
277
|
-
action: async () => await db.set(key, BigInt(1)),
|
|
278
|
-
wantOps: [],
|
|
279
|
-
wantErr: {name: 'TypeError'},
|
|
280
|
-
wantMetricsDelta: {
|
|
281
|
-
lockAcquires: 1,
|
|
282
|
-
lockReleases: 1,
|
|
283
|
-
writes: 1,
|
|
284
|
-
writesFailed: 1,
|
|
285
|
-
writesFinished: 1,
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: 'setSub ok',
|
|
290
|
-
action: async () => await db.setSub(key, ['s'], 'v2'),
|
|
291
|
-
wantOps: [
|
|
292
|
-
{
|
|
293
|
-
wantFns: ['get'],
|
|
294
|
-
wantMetricsDelta: {
|
|
295
|
-
lockAcquires: 1,
|
|
296
|
-
reads: 1,
|
|
297
|
-
readsFromDb: 1,
|
|
298
|
-
},
|
|
299
|
-
cbArgs: [[null, '{"s": "v1"}']],
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
wantFns: ['set'],
|
|
303
|
-
wantMetricsDelta: {
|
|
304
|
-
lockReleases: 1,
|
|
305
|
-
readsFinished: 1,
|
|
306
|
-
readsFromDbFinished: 1,
|
|
307
|
-
writes: 1,
|
|
308
|
-
writesToDb: 1,
|
|
309
|
-
},
|
|
310
|
-
cbArgs: [[null]],
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
wantErr: null,
|
|
314
|
-
wantMetricsDelta: {
|
|
315
|
-
writesFinished: 1,
|
|
316
|
-
writesToDbFinished: 1,
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: 'setSub db write error',
|
|
321
|
-
action: async () => await db.setSub(key, ['s'], 'v2'),
|
|
322
|
-
wantOps: [
|
|
323
|
-
{
|
|
324
|
-
wantFns: ['get'],
|
|
325
|
-
wantMetricsDelta: {
|
|
326
|
-
lockAcquires: 1,
|
|
327
|
-
reads: 1,
|
|
328
|
-
readsFromDb: 1,
|
|
329
|
-
},
|
|
330
|
-
cbArgs: [[null, '{"s": "v1"}']],
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
wantFns: ['set'],
|
|
334
|
-
wantMetricsDelta: {
|
|
335
|
-
lockReleases: 1,
|
|
336
|
-
readsFinished: 1,
|
|
337
|
-
readsFromDbFinished: 1,
|
|
338
|
-
writes: 1,
|
|
339
|
-
writesToDb: 1,
|
|
340
|
-
},
|
|
341
|
-
cbArgs: [[new Error('test')]],
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
wantErr: {message: 'test'},
|
|
345
|
-
wantMetricsDelta: {
|
|
346
|
-
writesFailed: 1,
|
|
347
|
-
writesFinished: 1,
|
|
348
|
-
writesToDbFailed: 1,
|
|
349
|
-
writesToDbFinished: 1,
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: 'setSub db read error',
|
|
354
|
-
action: async () => await db.setSub(key, ['s'], 'v2'),
|
|
355
|
-
wantOps: [
|
|
356
|
-
{
|
|
357
|
-
wantFns: ['get'],
|
|
358
|
-
wantMetricsDelta: {
|
|
359
|
-
lockAcquires: 1,
|
|
360
|
-
reads: 1,
|
|
361
|
-
readsFromDb: 1,
|
|
362
|
-
},
|
|
363
|
-
cbArgs: [[new Error('test')]],
|
|
364
|
-
},
|
|
365
|
-
],
|
|
366
|
-
wantErr: {message: 'test'},
|
|
367
|
-
wantMetricsDelta: {
|
|
368
|
-
lockReleases: 1,
|
|
369
|
-
readsFailed: 1,
|
|
370
|
-
readsFinished: 1,
|
|
371
|
-
readsFromDbFailed: 1,
|
|
372
|
-
readsFromDbFinished: 1,
|
|
373
|
-
writes: 1,
|
|
374
|
-
writesFailed: 1,
|
|
375
|
-
writesFinished: 1,
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: 'setSub json read error',
|
|
380
|
-
action: async () => await db.setSub(key, ['s'], 'v2'),
|
|
381
|
-
wantOps: [
|
|
382
|
-
{
|
|
383
|
-
wantFns: ['get'],
|
|
384
|
-
wantMetricsDelta: {
|
|
385
|
-
lockAcquires: 1,
|
|
386
|
-
reads: 1,
|
|
387
|
-
readsFromDb: 1,
|
|
388
|
-
},
|
|
389
|
-
cbArgs: [[null, 'ignore me -- this is intentionally invalid json']],
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
wantErr: {name: 'SyntaxError'},
|
|
393
|
-
wantMetricsDelta: {
|
|
394
|
-
lockReleases: 1,
|
|
395
|
-
readsFailed: 1,
|
|
396
|
-
readsFinished: 1,
|
|
397
|
-
readsFromDbFinished: 1,
|
|
398
|
-
writes: 1,
|
|
399
|
-
writesFailed: 1,
|
|
400
|
-
writesFinished: 1,
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
name: 'setSub update non-object error',
|
|
405
|
-
action: async () => await db.setSub(key, ['s'], 'v2'),
|
|
406
|
-
wantOps: [
|
|
407
|
-
{
|
|
408
|
-
wantFns: ['get'],
|
|
409
|
-
wantMetricsDelta: {
|
|
410
|
-
lockAcquires: 1,
|
|
411
|
-
reads: 1,
|
|
412
|
-
readsFromDb: 1,
|
|
413
|
-
},
|
|
414
|
-
cbArgs: [[null, '"foo"']],
|
|
415
|
-
},
|
|
416
|
-
],
|
|
417
|
-
wantErr: {message: /non-object/},
|
|
418
|
-
wantMetricsDelta: {
|
|
419
|
-
lockReleases: 1,
|
|
420
|
-
readsFinished: 1,
|
|
421
|
-
readsFromDbFinished: 1,
|
|
422
|
-
writes: 1,
|
|
423
|
-
writesFailed: 1,
|
|
424
|
-
writesFinished: 1,
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: 'setSub json write error',
|
|
429
|
-
action: async () => await db.setSub(key, ['s'], BigInt(1)),
|
|
430
|
-
wantOps: [
|
|
431
|
-
{
|
|
432
|
-
wantFns: ['get'],
|
|
433
|
-
wantMetricsDelta: {
|
|
434
|
-
lockAcquires: 1,
|
|
435
|
-
reads: 1,
|
|
436
|
-
readsFromDb: 1,
|
|
437
|
-
},
|
|
438
|
-
cbArgs: [[null, '{"s": "v1"}']],
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
wantErr: {name: 'TypeError'},
|
|
442
|
-
wantMetricsDelta: {
|
|
443
|
-
lockReleases: 1,
|
|
444
|
-
readsFinished: 1,
|
|
445
|
-
readsFromDbFinished: 1,
|
|
446
|
-
writes: 1,
|
|
447
|
-
writesFailed: 1,
|
|
448
|
-
writesFinished: 1,
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
name: 'doBulk ok',
|
|
453
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(`${key} second op`, 'v')]),
|
|
454
|
-
wantOps: [
|
|
455
|
-
{
|
|
456
|
-
wantFns: ['doBulk'],
|
|
457
|
-
wantMetricsDelta: {
|
|
458
|
-
lockAcquires: 2,
|
|
459
|
-
lockReleases: 2,
|
|
460
|
-
writes: 2,
|
|
461
|
-
writesToDb: 2,
|
|
462
|
-
},
|
|
463
|
-
cbArgs: [[null]],
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
wantErr: null,
|
|
467
|
-
wantMetricsDelta: {
|
|
468
|
-
writesFinished: 2,
|
|
469
|
-
writesToDbFinished: 2,
|
|
470
|
-
},
|
|
471
|
-
},
|
|
472
|
-
{
|
|
473
|
-
name: 'doBulk error, all retries ok',
|
|
474
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(`${key} second op`, 'v')]),
|
|
475
|
-
wantOps: [
|
|
476
|
-
{
|
|
477
|
-
wantFns: ['doBulk'],
|
|
478
|
-
wantMetricsDelta: {
|
|
479
|
-
lockAcquires: 2,
|
|
480
|
-
lockReleases: 2,
|
|
481
|
-
writes: 2,
|
|
482
|
-
writesToDb: 2,
|
|
483
|
-
},
|
|
484
|
-
cbArgs: [[new Error('injected doBulk error')]],
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
wantFns: ['set', 'set'],
|
|
488
|
-
wantMetricsDelta: {
|
|
489
|
-
writesToDbRetried: 2,
|
|
490
|
-
},
|
|
491
|
-
cbArgs: [[null], [null]],
|
|
492
|
-
},
|
|
493
|
-
],
|
|
494
|
-
wantErr: null,
|
|
495
|
-
wantMetricsDelta: {
|
|
496
|
-
writesFinished: 2,
|
|
497
|
-
writesToDbFinished: 2,
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: 'doBulk error, one of the retries fails',
|
|
502
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(`${key} second op`, 'v')]),
|
|
503
|
-
wantOps: [
|
|
504
|
-
{
|
|
505
|
-
wantFns: ['doBulk'],
|
|
506
|
-
wantMetricsDelta: {
|
|
507
|
-
lockAcquires: 2,
|
|
508
|
-
lockReleases: 2,
|
|
509
|
-
writes: 2,
|
|
510
|
-
writesToDb: 2,
|
|
511
|
-
},
|
|
512
|
-
cbArgs: [[new Error('injected doBulk error')]],
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
wantFns: ['set', 'set'],
|
|
516
|
-
wantMetricsDelta: {
|
|
517
|
-
writesToDbRetried: 2,
|
|
518
|
-
},
|
|
519
|
-
cbArgs: [[new Error('test')], [null]],
|
|
520
|
-
},
|
|
521
|
-
],
|
|
522
|
-
wantErr: {message: 'test'},
|
|
523
|
-
wantMetricsDelta: {
|
|
524
|
-
writesFailed: 1,
|
|
525
|
-
writesFinished: 2,
|
|
526
|
-
writesToDbFailed: 1,
|
|
527
|
-
writesToDbFinished: 2,
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
name: 'doBulk error, all retries fail',
|
|
532
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(`${key} second op`, 'v')]),
|
|
533
|
-
wantOps: [
|
|
534
|
-
{
|
|
535
|
-
wantFns: ['doBulk'],
|
|
536
|
-
wantMetricsDelta: {
|
|
537
|
-
lockAcquires: 2,
|
|
538
|
-
lockReleases: 2,
|
|
539
|
-
writes: 2,
|
|
540
|
-
writesToDb: 2,
|
|
541
|
-
},
|
|
542
|
-
cbArgs: [[new Error('injected doBulk error')]],
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
wantFns: ['set', 'set'],
|
|
546
|
-
wantMetricsDelta: {
|
|
547
|
-
writesToDbRetried: 2,
|
|
548
|
-
},
|
|
549
|
-
cbArgs: [[new Error('test1')], [new Error('test2')]],
|
|
550
|
-
},
|
|
551
|
-
],
|
|
552
|
-
wantErr: (err: any) => ['test1', 'test2'].includes(err.message),
|
|
553
|
-
wantMetricsDelta: {
|
|
554
|
-
writesFailed: 2,
|
|
555
|
-
writesFinished: 2,
|
|
556
|
-
writesToDbFailed: 2,
|
|
557
|
-
writesToDbFinished: 2,
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
name: 'obsoleted ok',
|
|
562
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(key, 'v2')]),
|
|
563
|
-
wantOps: [
|
|
564
|
-
{
|
|
565
|
-
wantFns: ['set'],
|
|
566
|
-
wantMetricsDelta: {
|
|
567
|
-
lockAcquires: 2,
|
|
568
|
-
lockAwaits: 1,
|
|
569
|
-
lockReleases: 2,
|
|
570
|
-
writes: 2,
|
|
571
|
-
writesObsoleted: 1,
|
|
572
|
-
writesToDb: 1,
|
|
573
|
-
},
|
|
574
|
-
cbArgs: [[null]],
|
|
575
|
-
},
|
|
576
|
-
],
|
|
577
|
-
wantErr: null,
|
|
578
|
-
wantMetricsDelta: {
|
|
579
|
-
writesFinished: 2,
|
|
580
|
-
writesToDbFinished: 1,
|
|
581
|
-
},
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
name: 'obsoleted error',
|
|
585
|
-
action: async () => await Promise.all([db.set(key, 'v'), db.set(key, 'v2')]),
|
|
586
|
-
wantOps: [
|
|
587
|
-
{
|
|
588
|
-
wantFns: ['set'],
|
|
589
|
-
wantMetricsDelta: {
|
|
590
|
-
lockAcquires: 2,
|
|
591
|
-
lockAwaits: 1,
|
|
592
|
-
lockReleases: 2,
|
|
593
|
-
writes: 2,
|
|
594
|
-
writesObsoleted: 1,
|
|
595
|
-
writesToDb: 1,
|
|
596
|
-
},
|
|
597
|
-
cbArgs: [[new Error('test')]],
|
|
598
|
-
},
|
|
599
|
-
],
|
|
600
|
-
wantErr: {message: 'test'},
|
|
601
|
-
wantMetricsDelta: {
|
|
602
|
-
writesFailed: 2,
|
|
603
|
-
writesFinished: 2,
|
|
604
|
-
writesToDbFailed: 1,
|
|
605
|
-
writesToDbFinished: 1,
|
|
606
|
-
},
|
|
607
|
-
},
|
|
608
|
-
];
|
|
609
|
-
for (const tc of tcs) {
|
|
610
|
-
it(tc.name, async () => {
|
|
611
|
-
const opStarts: any = [];
|
|
612
|
-
for (const fn of ['doBulk', 'get', 'remove', 'set']) {
|
|
613
|
-
mock.on(fn, (...args: any[]) => {
|
|
614
|
-
const opStart = opStarts.shift();
|
|
615
|
-
const cb = args.pop();
|
|
616
|
-
opStart.open([fn, cb]);
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
let before = {...db.metrics};
|
|
620
|
-
let actionDone;
|
|
621
|
-
// advance() triggers the next database operation(s), either by starting tc.action (if
|
|
622
|
-
// tc.action has not yet been started) or completing the previous operation(s) (if tc.action
|
|
623
|
-
// has been started).
|
|
624
|
-
let advance = () => { actionDone = tc.action(); };
|
|
625
|
-
for (const ops of tc.wantOps) {
|
|
626
|
-
// Provide a way for the mock database to tell us that a mocked database method has been
|
|
627
|
-
// called. The number of expected parallel operations for this iteration is
|
|
628
|
-
// ops.wantFns.length, so that is the number of Gates that are added to opStarts. Each
|
|
629
|
-
// Gate resolves to [fn, cb] where fn is the name of the mocked database method and cb is
|
|
630
|
-
// the mocked database method's callback.
|
|
631
|
-
for (let i = 0; i < ops.wantFns.length; ++i) opStarts.push(new Gate());
|
|
632
|
-
// Trigger the call(s) to the mock database method(s). This is scheduled to run in the
|
|
633
|
-
// future to ensure that advance() does not empty the opStarts array until after the
|
|
634
|
-
// Promise.all() call below has a chance to see all of the Promises in opStarts.
|
|
635
|
-
setImmediate(advance);
|
|
636
|
-
// Wait until the expected number of parallel database method calls have started.
|
|
637
|
-
const gotOps = await Promise.all(opStarts);
|
|
638
|
-
assertMetricsDelta(before, db.metrics, ops.wantMetricsDelta);
|
|
639
|
-
before = {...db.metrics};
|
|
640
|
-
const advanceFns: any = [];
|
|
641
|
-
for (const [gotFn, cb] of gotOps) {
|
|
642
|
-
const i = ops.wantFns.indexOf(gotFn);
|
|
643
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
644
|
-
assert(i >= 0, `unexpected mock database method call: ${gotFn}`);
|
|
645
|
-
ops.wantFns.splice(i, 1);
|
|
646
|
-
const [cbArgs] = ops.cbArgs.splice(i, 1);
|
|
647
|
-
advanceFns.push(() => cb(...cbArgs));
|
|
648
|
-
}
|
|
649
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
650
|
-
assert.equal(ops.wantFns.length, 0, `missing call(s): ${ops.wantFns.join(', ')}`);
|
|
651
|
-
// @ts-expect-error TS(7006): Parameter 'f' implicitly has an 'any' type.
|
|
652
|
-
advance = () => advanceFns.forEach((f) => f());
|
|
653
|
-
}
|
|
654
|
-
advance();
|
|
655
|
-
// @ts-expect-error TS(2769): No overload matches this call.
|
|
656
|
-
await (tc.wantErr ? assert.rejects(actionDone, tc.wantErr) : actionDone);
|
|
657
|
-
assertMetricsDelta(before, db.metrics, tc.wantMetricsDelta);
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
describe('lock contention', () => {
|
|
662
|
-
const tcs = [
|
|
663
|
-
{
|
|
664
|
-
name: 'get',
|
|
665
|
-
f: (key: any) => db.get(key),
|
|
666
|
-
wantMetrics: {lockAwaits: 1},
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: 'getSub',
|
|
670
|
-
fn: 'get',
|
|
671
|
-
f: (key: any) => db.getSub(key, ['s']),
|
|
672
|
-
wantMetrics: {lockAwaits: 1},
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
name: 'remove',
|
|
676
|
-
f: (key: any) => db.remove(key),
|
|
677
|
-
wantMetrics: {lockAwaits: 1},
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
name: 'set',
|
|
681
|
-
f: (key: any) => db.set(key, 'v'),
|
|
682
|
-
wantMetrics: {lockAwaits: 1},
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
name: 'setSub',
|
|
686
|
-
fn: 'set',
|
|
687
|
-
f: (key: any) => db.setSub(key, ['s'], 'v'),
|
|
688
|
-
wantMetrics: {lockAwaits: 1},
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
name: 'doBulk',
|
|
692
|
-
f: (key: any) => Promise.all([
|
|
693
|
-
db.set(key, 'v'),
|
|
694
|
-
db.set(`${key} second op`, 'v'),
|
|
695
|
-
]),
|
|
696
|
-
wantMetrics: {lockAcquires: 1, lockAwaits: 1},
|
|
697
|
-
},
|
|
698
|
-
];
|
|
699
|
-
for (const tc of tcs) {
|
|
700
|
-
if (tc.fn == null)
|
|
701
|
-
// @ts-expect-error TS(2322): Type 'string' is not assignable to type 'undefined... Remove this comment to see the full error message
|
|
702
|
-
{ tc.fn = tc.name; }
|
|
703
|
-
it(tc.name, async () => {
|
|
704
|
-
let finishRead;
|
|
705
|
-
const readStarted = new Promise((resolve) => {
|
|
706
|
-
mock.once('get', (key: any, cb: any) => {
|
|
707
|
-
// @ts-expect-error TS(2794): Expected 1 arguments, but got 0. Did you forget to... Remove this comment to see the full error message
|
|
708
|
-
resolve();
|
|
709
|
-
const val = '{"s": "v"}';
|
|
710
|
-
new Promise((resolve) => { finishRead = resolve; }).then(() => cb(null, val));
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
// Note: All contention tests should be with get() to ensure that all functions lock using
|
|
714
|
-
// the record's key.
|
|
715
|
-
const getP = db.get(key);
|
|
716
|
-
await readStarted;
|
|
717
|
-
mock.once(tc.fn, (...args: any[]) => {
|
|
718
|
-
// @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
|
|
719
|
-
assert(tc.fn !== 'get', 'value should have been cached');
|
|
720
|
-
args.pop()();
|
|
721
|
-
});
|
|
722
|
-
const before = {...db.metrics};
|
|
723
|
-
const opFinished = tc.f(key);
|
|
724
|
-
const flushed = db.flush(); // Speed up tests.
|
|
725
|
-
assertMetricsDelta(before, db.metrics, tc.wantMetrics);
|
|
726
|
-
// @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
|
727
|
-
finishRead();
|
|
728
|
-
await getP;
|
|
729
|
-
await opFinished;
|
|
730
|
-
await flushed;
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
});
|