Skip to content

Commit

Permalink
Add opt-in test for signal option
Browse files Browse the repository at this point in the history
Depends on Level/supports#22.
  • Loading branch information
vweevers committed Nov 19, 2022
1 parent 69c5ce7 commit f5ec53f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 17 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,8 @@ try {
}
```

Support of signals is indicated via [`db.supports.signals.iterators`](https://github.com/Level/supports#signals-object).

### `keyIterator`

A key iterator has the same interface as `iterator` except that its methods yield keys instead of entries. Usage is otherwise the same.
Expand Down Expand Up @@ -1623,7 +1625,7 @@ The `signal` option, if any and once signaled, should abort an in-progress `_nex
2. While a call is in progress, the implementation handles the signal
3. Once the signal is aborted, `abstract-level` rejects further calls.

A method like `_next()` therefore doesn't have to check the signal _before_ it start its asynchronous work, only _during_ that work. Whether to respect the signal and on which (potentially long-running) methods, is up to the implementation.
A method like `_next()` therefore doesn't have to check the signal _before_ it start its asynchronous work, only _during_ that work. Whether to respect the signal and on which (potentially long-running) methods, is up to the implementation. If supported, set `db.supports.signals.iterators` to `true` (via the manifest passed to the database constructor) which also enables relevant tests in the [test suite](#test-suite).

#### `iterator._next()`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"dependencies": {
"buffer": "^6.0.3",
"is-buffer": "^2.0.5",
"level-supports": "^5.0.0",
"level-supports": "github:Level/supports#add-signals",
"level-transcoder": "^1.0.1",
"maybe-combine-errors": "^1.0.0",
"module-error": "^1.0.1"
Expand Down
78 changes: 63 additions & 15 deletions test/iterator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,26 +133,74 @@ exports.sequence = function (test, testCommon) {
})
}

// At the moment, we can only be sure that signals are supported if the iterator is deferred
globalThis.AbortController && test(`${mode}().${method}() with aborted signal yields error`, async function (t) {
t.plan(2)
// 1) At the moment, we can only be sure that signals are supported if the iterator is deferred
if (globalThis.AbortController) {
test(`${mode}().${method}() with aborted signal yields error (deferred)`, async function (t) {
t.plan(2)

const db = testCommon.factory()
const ac = new globalThis.AbortController()
const it = db[mode]({ signal: ac.signal })
const db = testCommon.factory()
const ac = new globalThis.AbortController()
const it = db[mode]({ signal: ac.signal })

t.is(db.status, 'opening', 'is deferred')
ac.abort()
t.is(db.status, 'opening', 'is deferred')
ac.abort()

try {
try {
await it[method](...requiredArgs)
} catch (err) {
t.is(err.code, 'LEVEL_ABORTED')
}

await it.close()
return db.close()
})
}

// 2) Unless the implementation opts-in
if (globalThis.AbortController && testCommon.supports.signals && testCommon.supports.signals.iterators) {
test(`${mode}().${method}() with signal yields error when aborted`, async function (t) {
t.plan(1)

const db = testCommon.factory()

await db.open()
await db.batch().put('a', 'a').put('b', 'b').write()

const ac = new globalThis.AbortController()
const it = db[mode]({ signal: ac.signal })
const promise = it[method](...requiredArgs)

ac.abort()

try {
await promise
} catch (err) {
t.is(err.code, 'LEVEL_ABORTED')
}

await it.close()
return db.close()
})

test(`${mode}().${method}() with non-aborted signal`, async function (t) {
const db = testCommon.factory()

await db.open()
await db.batch().put('a', 'a').put('b', 'b').write()

const ac = new globalThis.AbortController()
const it = db[mode]({ signal: ac.signal })

// We're merely testing that this does not throw. And implicitly testing (through
// coverage) that abort listeners are removed. An implementation might choose to
// periodically check signal.aborted instead of using an abort listener, so we
// can't directly assert that cleanup indeed happens.
await it[method](...requiredArgs)
} catch (err) {
t.is(err.code, 'LEVEL_ABORTED')
}
await it.close()

await it.close()
return db.close()
})
return db.close()
})
}
}
}
}
Expand Down

0 comments on commit f5ec53f

Please sign in to comment.