Skip to content

Commit 454aec1

Browse files
committed
Add tests
1 parent 41a4bcb commit 454aec1

7 files changed

+406
-16
lines changed

Diff for: test/async-iterator-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ exports.asyncIterator = function (test, testCommon) {
5353
await db.close()
5454
})
5555

56-
testCommon.supports.snapshots && test(`for await...of ${mode}() (deferred, with snapshot)`, async function (t) {
56+
testCommon.supports.implicitSnapshots && test(`for await...of ${mode}() (deferred, with snapshot)`, async function (t) {
5757
t.plan(2)
5858

5959
const db = testCommon.factory()

Diff for: test/index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ function suite (options) {
4343
require('./iterator-seek-test').all(test, testCommon)
4444
}
4545

46-
if (testCommon.supports.snapshots) {
46+
if (testCommon.supports.implicitSnapshots) {
4747
require('./iterator-snapshot-test').all(test, testCommon)
4848
} else {
4949
require('./iterator-no-snapshot-test').all(test, testCommon)
5050
}
5151

52+
if (testCommon.supports.explicitSnapshots) {
53+
require('./iterator-explicit-snapshot-test').all(test, testCommon)
54+
}
55+
5256
require('./clear-test').all(test, testCommon)
5357
require('./clear-range-test').all(test, testCommon)
5458
require('./sublevel-test').all(test, testCommon)

Diff for: test/iterator-explicit-snapshot-test.js

+356
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
'use strict'
2+
3+
const traits = require('./traits')
4+
5+
exports.traits = function (test, testCommon) {
6+
// TODO: document (or fix...) that deferred open is not supported
7+
traits.open('snapshot()', testCommon, { deferred: false }, async function (t, db) {
8+
const snapshot = db.snapshot()
9+
return snapshot.close()
10+
})
11+
12+
traits.closed('snapshot()', testCommon, async function (t, db) {
13+
db.snapshot()
14+
})
15+
}
16+
17+
exports.get = function (test, testCommon) {
18+
const { testFresh, testClose } = testFactory(test, testCommon)
19+
20+
testFresh('get() changed entry from explicit snapshot', async function (t, db) {
21+
t.plan(3)
22+
23+
await db.put('abc', 'before')
24+
const snapshot = db.snapshot()
25+
await db.put('abc', 'after')
26+
27+
t.is(await db.get('abc'), 'after')
28+
t.is(await db.get('abc', { snapshot }), 'before')
29+
t.is(await db.get('other', { snapshot }), undefined)
30+
31+
return snapshot.close()
32+
})
33+
34+
testFresh('get() deleted entry from explicit snapshot', async function (t, db) {
35+
t.plan(3)
36+
37+
await db.put('abc', 'before')
38+
const snapshot = db.snapshot()
39+
await db.del('abc')
40+
41+
t.is(await db.get('abc'), undefined)
42+
t.is(await db.get('abc', { snapshot }), 'before')
43+
t.is(await db.get('other', { snapshot }), undefined)
44+
45+
return snapshot.close()
46+
})
47+
48+
testFresh('get() non-existent entry from explicit snapshot', async function (t, db) {
49+
t.plan(2)
50+
51+
const snapshot = db.snapshot()
52+
await db.put('abc', 'after')
53+
54+
t.is(await db.get('abc'), 'after')
55+
t.is(await db.get('abc', { snapshot }), undefined)
56+
57+
return snapshot.close()
58+
})
59+
60+
testFresh('get() entries from multiple explicit snapshots', async function (t, db) {
61+
const snapshots = []
62+
const iterations = 100
63+
64+
t.plan(iterations)
65+
66+
for (let i = 0; i < iterations; i++) {
67+
await db.put('number', i.toString())
68+
snapshots.push(db.snapshot())
69+
}
70+
71+
for (let i = 0; i < iterations; i++) {
72+
const snapshot = snapshots[i]
73+
const value = i.toString()
74+
75+
t.is(await db.get('number', { snapshot }), value)
76+
}
77+
78+
return Promise.all(snapshots.map(x => x.close()))
79+
})
80+
81+
testClose('get()', async function (db, snapshot) {
82+
return db.get('xyz', { snapshot })
83+
})
84+
}
85+
86+
exports.getMany = function (test, testCommon) {
87+
const { testFresh, testClose } = testFactory(test, testCommon)
88+
89+
testFresh('getMany() entries from explicit snapshot', async function (t, db) {
90+
t.plan(3)
91+
92+
await db.put('a', '1')
93+
await db.put('b', '2')
94+
await db.put('c', '3')
95+
96+
const snapshot = db.snapshot()
97+
98+
await db.put('a', 'abc')
99+
await db.del('b')
100+
await db.put('c', 'xyz')
101+
102+
t.same(await db.getMany(['a', 'b', 'c']), ['abc', undefined, 'xyz'])
103+
t.same(await db.getMany(['a', 'b', 'c'], { snapshot }), ['1', '2', '3'])
104+
t.same(await db.getMany(['a', 'b', 'c']), ['abc', undefined, 'xyz'], 'no side effects')
105+
106+
return snapshot.close()
107+
})
108+
109+
testClose('getMany()', async function (db, snapshot) {
110+
return db.getMany(['xyz'], { snapshot })
111+
})
112+
}
113+
114+
exports.iterator = function (test, testCommon) {
115+
const { testFresh, testClose } = testFactory(test, testCommon)
116+
117+
testFresh('iterator(), keys(), values() with explicit snapshot', async function (t, db) {
118+
t.plan(10)
119+
120+
await db.put('a', '1')
121+
await db.put('b', '2')
122+
await db.put('c', '3')
123+
124+
const snapshot = db.snapshot()
125+
126+
await db.put('a', 'after')
127+
await db.del('b')
128+
await db.put('c', 'after')
129+
await db.put('d', 'after')
130+
131+
t.same(
132+
await db.iterator().all(),
133+
[['a', 'after'], ['c', 'after'], ['d', 'after']],
134+
'data was written'
135+
)
136+
137+
for (const fn of [all, nextv, next]) {
138+
t.same(await fn(db.iterator({ snapshot })), [['a', '1'], ['b', '2'], ['c', '3']], 'iterator')
139+
t.same(await fn(db.keys({ snapshot })), ['a', 'b', 'c'], 'keys')
140+
t.same(await fn(db.values({ snapshot })), ['1', '2', '3'], 'values')
141+
}
142+
143+
async function all (iterator) {
144+
return iterator.all()
145+
}
146+
147+
async function nextv (iterator) {
148+
try {
149+
return iterator.nextv(10)
150+
} finally {
151+
await iterator.close()
152+
}
153+
}
154+
155+
async function next (iterator) {
156+
try {
157+
const entries = []
158+
let entry
159+
160+
while ((entry = await iterator.next()) !== undefined) {
161+
entries.push(entry)
162+
}
163+
164+
return entries
165+
} finally {
166+
await iterator.close()
167+
}
168+
}
169+
170+
return snapshot.close()
171+
})
172+
173+
// Test that every iterator type and read method checks snapshot state
174+
for (const type of ['iterator', 'keys', 'values']) {
175+
testClose(`${type}().all()`, async function (db, snapshot) {
176+
return db[type]({ snapshot }).all()
177+
})
178+
179+
testClose(`${type}().next()`, async function (db, snapshot) {
180+
const iterator = db[type]({ snapshot })
181+
182+
try {
183+
await iterator.next()
184+
} finally {
185+
iterator.close()
186+
}
187+
})
188+
189+
testClose(`${type}().nextv()`, async function (db, snapshot) {
190+
const iterator = db[type]({ snapshot })
191+
192+
try {
193+
await iterator.nextv(10)
194+
} finally {
195+
iterator.close()
196+
}
197+
})
198+
}
199+
}
200+
201+
exports.clear = function (test, testCommon) {
202+
const { testFresh, testClose } = testFactory(test, testCommon)
203+
204+
testFresh('clear() entries from explicit snapshot', async function (t, db) {
205+
t.plan(2)
206+
207+
await db.put('a', 'xyz')
208+
const snapshot = db.snapshot()
209+
210+
await db.put('b', 'xyz')
211+
await db.clear({ snapshot })
212+
213+
t.same(await db.keys().all(), ['b'])
214+
t.same(await db.keys({ snapshot }).all(), ['a'])
215+
216+
return snapshot.close()
217+
})
218+
219+
testFresh('clear() entries from empty explicit snapshot', async function (t, db) {
220+
t.plan(2)
221+
222+
const snapshot = db.snapshot()
223+
224+
await db.put('a', 'xyz')
225+
await db.clear({ snapshot })
226+
227+
t.same(await db.keys().all(), ['a'])
228+
t.same(await db.keys({ snapshot }).all(), [])
229+
230+
return snapshot.close()
231+
})
232+
233+
testClose('clear()', async function (db, snapshot) {
234+
return db.clear({ snapshot })
235+
})
236+
}
237+
238+
exports.cleanup = function (test, testCommon) {
239+
test('snapshot is closed on database close', async function (t) {
240+
const db = testCommon.factory()
241+
242+
// TODO: behavior of sublevels does not match, because snapshot is
243+
// owned by parent database rather than sublevel, so closing the
244+
// sublevel does not automatically close the snapshot.
245+
if (db.parent) {
246+
return db.close()
247+
}
248+
249+
t.plan(1)
250+
251+
await db.open()
252+
const snapshot = db.snapshot()
253+
const promise = db.close()
254+
255+
try {
256+
snapshot.ref()
257+
} catch (err) {
258+
t.is(err.code, 'LEVEL_SNAPSHOT_NOT_OPEN')
259+
}
260+
261+
return promise
262+
})
263+
264+
test('snapshot is closed along with iterator', async function (t) {
265+
const db = testCommon.factory()
266+
267+
// TODO: behavior of sublevels does not match, because snapshot is
268+
// owned by parent database rather than sublevel.
269+
if (db.parent) {
270+
return db.close()
271+
}
272+
273+
t.plan(2)
274+
275+
await db.open()
276+
await db.put('beep', 'boop')
277+
278+
// These resources have a potentially tricky relationship. If all is well,
279+
// db.close() calls both snapshot.close() and iterator.close() in parallel,
280+
// and snapshot.close() and iterator.close() wait on the read. Crucially,
281+
// closing the snapshot only waits for individual operations on the iterator
282+
// rather than for the entire iterator to be closed (which may never happen).
283+
const snapshot = db.snapshot()
284+
const iterator = db.iterator({ snapshot })
285+
const readPromise = iterator.all()
286+
const closePromise = db.close()
287+
288+
try {
289+
snapshot.ref()
290+
} catch (err) {
291+
t.is(err.code, 'LEVEL_SNAPSHOT_NOT_OPEN', 'snapshot is closing')
292+
}
293+
294+
try {
295+
await iterator.next()
296+
} catch (err) {
297+
// We effectively also assert here that LEVEL_ITERATOR_NOT_OPEN takes
298+
// precedence over LEVEL_SNAPSHOT_NOT_OPEN.
299+
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN', 'iterator is closing')
300+
}
301+
302+
return Promise.all([readPromise, closePromise])
303+
})
304+
}
305+
306+
exports.all = function (test, testCommon) {
307+
exports.traits(test, testCommon)
308+
exports.get(test, testCommon)
309+
exports.getMany(test, testCommon)
310+
exports.iterator(test, testCommon)
311+
exports.clear(test, testCommon)
312+
exports.cleanup(test, testCommon)
313+
}
314+
315+
function testFactory (test, testCommon) {
316+
const testFresh = function (name, run) {
317+
test(name, async function (t) {
318+
const db = testCommon.factory()
319+
await db.open()
320+
await run(t, db)
321+
return db.close()
322+
})
323+
}
324+
325+
const testClose = function (name, run) {
326+
testFresh(`${name} after closing explicit snapshot`, async function (t, db) {
327+
t.plan(1)
328+
329+
const snapshot = db.snapshot()
330+
await snapshot.close()
331+
332+
try {
333+
await run(db, snapshot)
334+
} catch (err) {
335+
t.is(err.code, 'LEVEL_SNAPSHOT_NOT_OPEN')
336+
}
337+
})
338+
339+
testFresh(`${name} while closing explicit snapshot`, async function (t, db) {
340+
t.plan(1)
341+
342+
const snapshot = db.snapshot()
343+
const promise = snapshot.close()
344+
345+
try {
346+
await run(db, snapshot)
347+
} catch (err) {
348+
t.is(err.code, 'LEVEL_SNAPSHOT_NOT_OPEN')
349+
}
350+
351+
return promise
352+
})
353+
}
354+
355+
return { testFresh, testClose }
356+
}

Diff for: test/iterator-seek-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ exports.seek = function (test, testCommon) {
170170
return db.close()
171171
})
172172

173-
if (testCommon.supports.snapshots) {
173+
if (testCommon.supports.implicitSnapshots) {
174174
for (const reverse of [false, true]) {
175175
for (const deferred of [false, true]) {
176176
test(`${mode}().seek() respects snapshot (reverse: ${reverse}, deferred: ${deferred})`, async function (t) {

0 commit comments

Comments
 (0)