[Pkg-javascript-commits] [node-abstract-leveldown] 01/02: Imported Upstream version 0.12.3

Andrew Kelley andrewrk-guest at moszumanska.debian.org
Sun Jun 29 05:48:18 UTC 2014


This is an automated email from the git hooks/post-receive script.

andrewrk-guest pushed a commit to branch master
in repository node-abstract-leveldown.

commit 1d4aab7120fcdb6a796ec41b524cf8327319a5da
Author: Andrew Kelley <superjoe30 at gmail.com>
Date:   Sun Jun 29 05:40:11 2014 +0000

    Imported Upstream version 0.12.3
---
 .gitignore                        |   1 +
 .jshintrc                         |  60 ++++
 .travis.yml                       |  10 +
 CHANGELOG.md                      |  18 ++
 CONTRIBUTING.md                   |  27 ++
 LICENSE                           |  39 +++
 README.md                         | 159 +++++++++++
 abstract-chained-batch.js         |  81 ++++++
 abstract-iterator.js              |  49 ++++
 abstract-leveldown.js             | 258 ++++++++++++++++++
 abstract/approximate-size-test.js | 121 +++++++++
 abstract/batch-test.js            | 144 ++++++++++
 abstract/chained-batch-test.js    | 222 +++++++++++++++
 abstract/close-test.js            |  24 ++
 abstract/del-test.js              |  77 ++++++
 abstract/get-test.js              | 125 +++++++++
 abstract/iterator-test.js         | 460 +++++++++++++++++++++++++++++++
 abstract/leveldown-test.js        |  17 ++
 abstract/open-test.js             |  96 +++++++
 abstract/put-get-del-test.js      | 167 ++++++++++++
 abstract/put-test.js              |  92 +++++++
 abstract/ranges-test.js           | 435 +++++++++++++++++++++++++++++
 abstract/util.js                  |   8 +
 package.json                      |  46 ++++
 test.js                           | 559 ++++++++++++++++++++++++++++++++++++++
 testCommon.js                     |  75 +++++
 26 files changed, 3370 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..ba4514a
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,60 @@
+{
+    "predef": [ ]
+  , "bitwise": false
+  , "camelcase": false
+  , "curly": false
+  , "eqeqeq": false
+  , "forin": false
+  , "immed": false
+  , "latedef": false
+  , "newcap": true
+  , "noarg": true
+  , "noempty": true
+  , "nonew": true
+  , "plusplus": false
+  , "quotmark": true
+  , "regexp": false
+  , "undef": true
+  , "unused": true
+  , "strict": false
+  , "trailing": true
+  , "maxlen": 120
+  , "asi": true
+  , "boss": true
+  , "debug": true
+  , "eqnull": true
+  , "esnext": true
+  , "evil": true
+  , "expr": true
+  , "funcscope": false
+  , "globalstrict": false
+  , "iterator": false
+  , "lastsemic": true
+  , "laxbreak": true
+  , "laxcomma": true
+  , "loopfunc": true
+  , "multistr": false
+  , "onecase": false
+  , "proto": false
+  , "regexdash": false
+  , "scripturl": true
+  , "smarttabs": false
+  , "shadow": false
+  , "sub": true
+  , "supernew": false
+  , "validthis": true
+  , "browser": true
+  , "couch": false
+  , "devel": false
+  , "dojo": false
+  , "mootools": false
+  , "node": true
+  , "nonstandard": true
+  , "prototypejs": false
+  , "rhino": false
+  , "worker": true
+  , "wsh": false
+  , "nomen": false
+  , "onevar": true
+  , "passfail": false
+}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fe3f4eb
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
+branches:
+  only:
+    - master
+notifications:
+  email:
+    - rod at vagg.org
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..89c9013
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,18 @@
+### 0.11.1 - Nov 15 2013
+ * Adjust approximate-size-test.js to account for snappy compression
+
+### 0.11.0 - Oct 14 2013
+ * Introduce _setupIteratorOptions() method to fix options object prior to _iterator() call; makes working with gt/gte/lt/lte options a little easier (@rvagg)
+
+### 0.10.2 - Sep 6 2013
+
+ * Refactor duplicated versions of isTypedArray into util.js (@rvagg)
+ * Refactor duplicated versions of 'NotFound' checks into util.js, fixed too-strict version in get-test.js (@rvagg)
+
+### 0.10.1 - Aug 29 2013
+
+ * Relax check for 'Not Found: ' in error message to be case insensitive in get-test.js (@rvagg)
+
+### 0.10.0 - Aug 19 2013
+
+ * Added test for gt, gte, lt, lte ranges (@dominictarr)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2641fd0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# LevelUP is an OPEN Open Source Project
+
+-----------------------------------------
+
+## What?
+
+Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
+
+## Rules
+
+There are a few basic ground-rules for contributors:
+
+1. **No `--force` pushes** or modifying the Git history in any way.
+1. **Non-master branches** ought to be used for ongoing work.
+1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.
+1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor.
+1. Contributors should attempt to adhere to the prevailing code-style.
+
+## Releases
+
+Declaring formal releases remains the prerogative of the project maintainer.
+
+## Changes to this arrangement
+
+This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
+
+-----------------------------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..76d07a0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,39 @@
+Copyright 2012, Rod Vagg (the "Original Author")
+All rights reserved.
+
+MIT +no-false-attribs License
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+Distributions of all or part of the Software intended to be used
+by the recipients as they would use the unmodified Software,
+containing modifications that substantially alter, remove, or
+disable functionality of the Software, outside of the documented
+configuration mechanisms provided by the Software, shall be
+modified such that the Original Author's bug reporting email
+addresses and urls are either replaced with the contact information
+of the parties responsible for the changes, or removed entirely.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+Except where noted, this license applies to any and all software
+programs and associated documentation files created by the
+Original Author, when distributed with the Software.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1db6d6b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,159 @@
+# Abstract LevelDOWN [![Build Status](https://secure.travis-ci.org/rvagg/abstract-leveldown.png)](http://travis-ci.org/rvagg/abstract-leveldown)
+
+[![NPM](https://nodei.co/npm/abstract-leveldown.png?downloads=true)](https://nodei.co/npm/abstract-leveldown/)
+[![NPM](https://nodei.co/npm-dl/abstract-leveldown.png?months=6)](https://nodei.co/npm/abstract-leveldown/)
+
+An abstract prototype matching the **[LevelDOWN](https://github.com/rvagg/node-leveldown/)** API. Useful for extending **[LevelUP](https://github.com/rvagg/node-levelup)** functionality by providing a replacement to LevelDOWN.
+
+As of version 0.7, LevelUP allows you to pass a `'db'` option when you create a new instance. This will override the default LevelDOWN store with a LevelDOWN API compatible object.
+
+**Abstract LevelDOWN** provides a simple, operational *noop* base prototype that's ready for extending. By default, all operations have sensible "noops" (operations that essentially do nothing). For example, simple operations such as `.open(callback)` and `.close(callback)` will simply invoke the callback (on a *next tick*). More complex operations  perform sensible actions, for example: `.get(key, callback)` will always return a `'NotFound'` `Error` on the callback.
+
+You add functionality by implementing the underscore versions of the operations. For example, to implement a `put()` operation you add a `_put()` method to your object. Each of these underscore methods override the default *noop* operations and are always provided with **consistent arguments**, regardless of what is passed in by the client.
+
+Additionally, all methods provide argument checking and sensible defaults for optional arguments. All bad-argument errors are compatible with LevelDOWN (they pass the LevelDOWN method arguments tests). For example, if you call `.open()` without a callback argument you'll get an `Error('open() requires a callback argument')`. Where optional arguments are involved, your underscore methods will receive sensible defaults. A `.get(key, callback)` will pass through to a `._get(key, options, ca [...]
+
+## Example
+
+A simplistic in-memory LevelDOWN replacement
+
+```js
+var util = require('util')
+  , AbstractLevelDOWN = require('./').AbstractLevelDOWN
+
+// constructor, passes through the 'location' argument to the AbstractLevelDOWN constructor
+function FakeLevelDOWN (location) {
+  AbstractLevelDOWN.call(this, location)
+}
+
+// our new prototype inherits from AbstractLevelDOWN
+util.inherits(FakeLevelDOWN, AbstractLevelDOWN)
+
+// implement some methods
+
+FakeLevelDOWN.prototype._open = function (options, callback) {
+  // initialise a memory storage object
+  this._store = {}
+  // optional use of nextTick to be a nice async citizen
+  process.nextTick(function () { callback(null, this) }.bind(this))
+}
+
+FakeLevelDOWN.prototype._put = function (key, value, options, callback) {
+  key = '_' + key // safety, to avoid key='__proto__'-type skullduggery 
+  this._store[key] = value
+  process.nextTick(callback)
+}
+
+FakeLevelDOWN.prototype._get = function (key, options, callback) {
+  var value = this._store['_' + key]
+  if (value === undefined) {
+    // 'NotFound' error, consistent with LevelDOWN API
+    return process.nextTick(function () { callback(new Error('NotFound')) })
+  }
+  process.nextTick(function () {
+    callback(null, value)
+  })
+}
+
+FakeLevelDOWN.prototype._del = function (key, options, callback) {
+  delete this._store['_' + key]
+  process.nextTick(callback)
+}
+
+// now use it in LevelUP
+
+var levelup = require('levelup')
+
+var db = levelup('/who/cares/', {
+  // the 'db' option replaces LevelDOWN
+  db: function (location) { return new FakeLevelDOWN(location) }
+})
+
+db.put('foo', 'bar', function (err) {
+  if (err) throw err
+  db.get('foo', function (err, value) {
+    if (err) throw err
+    console.log('Got foo =', value)
+  })
+})
+```
+
+See [MemDOWN](https://github.com/rvagg/memdown/) if you are looking for a complete in-memory replacement for LevelDOWN.
+
+## Extensible API
+
+Remember that each of these methods, if you implement them, will receive exactly the number and order of arguments described. Optional arguments will be converted to sensible defaults.
+
+### AbstractLevelDOWN(location)
+### AbstractLevelDOWN#_open(options, callback)
+### AbstractLevelDOWN#_close(callback)
+### AbstractLevelDOWN#_get(key, options, callback)
+### AbstractLevelDOWN#_put(key, value, options, callback)
+### AbstractLevelDOWN#_del(key, options, callback)
+### AbstractLevelDOWN#_batch(array, options, callback)
+
+If `batch()` is called without argument or with only an options object then it should return a `Batch` object with chainable methods. Otherwise it will invoke a classic batch operation.
+
+### AbstractLevelDOWN#_chainedBatch()
+
+By default an `batch()` operation without argument returns a blank `AbstractChainedBatch` object. The prototype is available on the main exports for you to extend. If you want to implement chainable batch operations then you should extend the `AbstractChaindBatch` and return your object in the `_chainedBatch()` method.
+
+### AbstractLevelDOWN#_approximateSize(start, end, callback)
+### AbstractLevelDOWN#_iterator(options)
+
+By default an `iterator()` operation returns a blank `AbstractIterator` object. The prototype is available on the main exports for you to extend. If you want to implement iterator operations then you should extend the `AbstractIterator` and return your object in the `_iterator(options)` method.
+
+`AbstractIterator` implements the basic state management found in LevelDOWN. It keeps track of when a `next()` is in progress and when an `end()` has been called so it doesn't allow concurrent `next()` calls, it does it allow `end()` while a `next()` is in progress and it doesn't allow either `next()` or `end()` after `end()` has been called.
+
+### AbstractIterator(db)
+
+Provided with the current instance of `AbstractLevelDOWN` by default.
+
+### AbstractIterator#_next(callback)
+### AbstractIterator#_end(callback)
+
+### AbstractChainedBatch
+Provided with the current instance of `AbstractLevelDOWN` by default.
+
+### AbstractChainedBatch#_put(key, value)
+### AbstractChainedBatch#_del(key)
+### AbstractChainedBatch#_clear()
+### AbstractChainedBatch#_write(options, callback)
+
+<a name="contributing"></a>
+Contributing
+------------
+
+Abstract LevelDOWN is an **OPEN Open Source Project**. This means that:
+
+> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
+
+See the [CONTRIBUTING.md](https://github.com/rvagg/node-levelup/blob/master/CONTRIBUTING.md) file for more details.
+
+### Contributors
+
+Abstract LevelDOWN is only possible due to the excellent work of the following contributors:
+
+<table><tbody>
+<tr><th align="left">Rod Vagg</th><td><a href="https://github.com/rvagg">GitHub/rvagg</a></td><td><a href="http://twitter.com/rvagg">Twitter/@rvagg</a></td></tr>
+<tr><th align="left">John Chesley</th><td><a href="https://github.com/chesles/">GitHub/chesles</a></td><td><a href="http://twitter.com/chesles">Twitter/@chesles</a></td></tr>
+<tr><th align="left">Jake Verbaten</th><td><a href="https://github.com/raynos">GitHub/raynos</a></td><td><a href="http://twitter.com/raynos2">Twitter/@raynos2</a></td></tr>
+<tr><th align="left">Dominic Tarr</th><td><a href="https://github.com/dominictarr">GitHub/dominictarr</a></td><td><a href="http://twitter.com/dominictarr">Twitter/@dominictarr</a></td></tr>
+<tr><th align="left">Max Ogden</th><td><a href="https://github.com/maxogden">GitHub/maxogden</a></td><td><a href="http://twitter.com/maxogden">Twitter/@maxogden</a></td></tr>
+<tr><th align="left">Lars-Magnus Skog</th><td><a href="https://github.com/ralphtheninja">GitHub/ralphtheninja</a></td><td><a href="http://twitter.com/ralphtheninja">Twitter/@ralphtheninja</a></td></tr>
+<tr><th align="left">David Björklund</th><td><a href="https://github.com/kesla">GitHub/kesla</a></td><td><a href="http://twitter.com/david_bjorklund">Twitter/@david_bjorklund</a></td></tr>
+<tr><th align="left">Julian Gruber</th><td><a href="https://github.com/juliangruber">GitHub/juliangruber</a></td><td><a href="http://twitter.com/juliangruber">Twitter/@juliangruber</a></td></tr>
+<tr><th align="left">Paolo Fragomeni</th><td><a href="https://github.com/hij1nx">GitHub/hij1nx</a></td><td><a href="http://twitter.com/hij1nx">Twitter/@hij1nx</a></td></tr>
+<tr><th align="left">Anton Whalley</th><td><a href="https://github.com/No9">GitHub/No9</a></td><td><a href="https://twitter.com/antonwhalley">Twitter/@antonwhalley</a></td></tr>
+<tr><th align="left">Matteo Collina</th><td><a href="https://github.com/mcollina">GitHub/mcollina</a></td><td><a href="https://twitter.com/matteocollina">Twitter/@matteocollina</a></td></tr>
+<tr><th align="left">Pedro Teixeira</th><td><a href="https://github.com/pgte">GitHub/pgte</a></td><td><a href="https://twitter.com/pgte">Twitter/@pgte</a></td></tr>
+<tr><th align="left">James Halliday</th><td><a href="https://github.com/substack">GitHub/substack</a></td><td><a href="https://twitter.com/substack">Twitter/@substack</a></td></tr>
+</tbody></table>
+
+<a name="licence"></a>
+Licence & copyright
+-------------------
+
+Copyright (c) 2012-2013 Abstract LevelDOWN contributors (listed above).
+
+Abstract LevelDOWN is licensed under an MIT +no-false-attribs license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.
diff --git a/abstract-chained-batch.js b/abstract-chained-batch.js
new file mode 100644
index 0000000..7e48059
--- /dev/null
+++ b/abstract-chained-batch.js
@@ -0,0 +1,81 @@
+/* Copyright (c) 2013 Rod Vagg, MIT License */
+
+function AbstractChainedBatch (db) {
+  this._db         = db
+  this._operations = []
+  this._written    = false
+}
+
+AbstractChainedBatch.prototype._checkWritten = function () {
+  if (this._written)
+    throw new Error('write() already called on this batch')
+}
+
+AbstractChainedBatch.prototype.put = function (key, value) {
+  this._checkWritten()
+
+  var err = this._db._checkKeyValue(key, 'key', this._db._isBuffer)
+  if (err) throw err
+  err = this._db._checkKeyValue(value, 'value', this._db._isBuffer)
+  if (err) throw err
+
+  if (!this._db._isBuffer(key)) key = String(key)
+  if (!this._db._isBuffer(value)) value = String(value)
+
+  if (typeof this._put == 'function' )
+    this._put(key, value)
+  else
+    this._operations.push({ type: 'put', key: key, value: value })
+
+  return this
+}
+
+AbstractChainedBatch.prototype.del = function (key) {
+  this._checkWritten()
+
+  var err = this._db._checkKeyValue(key, 'key', this._db._isBuffer)
+  if (err) throw err
+
+  if (!this._db._isBuffer(key)) key = String(key)
+
+  if (typeof this._del == 'function' )
+    this._del(key)
+  else
+    this._operations.push({ type: 'del', key: key })
+
+  return this
+}
+
+AbstractChainedBatch.prototype.clear = function () {
+  this._checkWritten()
+
+  this._operations = []
+
+  if (typeof this._clear == 'function' )
+    this._clear()
+
+  return this
+}
+
+AbstractChainedBatch.prototype.write = function (options, callback) {
+  this._checkWritten()
+
+  if (typeof options == 'function')
+    callback = options
+  if (typeof callback != 'function')
+    throw new Error('write() requires a callback argument')
+  if (typeof options != 'object')
+    options = {}
+
+  this._written = true
+
+  if (typeof this._write == 'function' )
+    return this._write(callback)
+
+  if (typeof this._db._batch == 'function')
+    return this._db._batch(this._operations, options, callback)
+
+  process.nextTick(callback)
+}
+
+module.exports = AbstractChainedBatch
\ No newline at end of file
diff --git a/abstract-iterator.js b/abstract-iterator.js
new file mode 100644
index 0000000..04ed6a5
--- /dev/null
+++ b/abstract-iterator.js
@@ -0,0 +1,49 @@
+/* Copyright (c) 2013 Rod Vagg, MIT License */
+
+function AbstractIterator (db) {
+  this.db = db
+  this._ended = false
+  this._nexting = false
+}
+
+AbstractIterator.prototype.next = function (callback) {
+  var self = this
+
+  if (typeof callback != 'function')
+    throw new Error('next() requires a callback argument')
+
+  if (self._ended)
+    return callback(new Error('cannot call next() after end()'))
+  if (self._nexting)
+    return callback(new Error('cannot call next() before previous next() has completed'))
+
+  self._nexting = true
+  if (typeof self._next == 'function') {
+    return self._next(function () {
+      self._nexting = false
+      callback.apply(null, arguments)
+    })
+  }
+
+  process.nextTick(function () {
+    self._nexting = false
+    callback()
+  })
+}
+
+AbstractIterator.prototype.end = function (callback) {
+  if (typeof callback != 'function')
+    throw new Error('end() requires a callback argument')
+
+  if (this._ended)
+    return callback(new Error('end() already called on iterator'))
+
+  this._ended = true
+
+  if (typeof this._end == 'function')
+    return this._end(callback)
+
+  process.nextTick(callback)
+}
+
+module.exports = AbstractIterator
diff --git a/abstract-leveldown.js b/abstract-leveldown.js
new file mode 100644
index 0000000..e56a641
--- /dev/null
+++ b/abstract-leveldown.js
@@ -0,0 +1,258 @@
+/* Copyright (c) 2013 Rod Vagg, MIT License */
+
+var xtend                = require('xtend')
+  , AbstractIterator     = require('./abstract-iterator')
+  , AbstractChainedBatch = require('./abstract-chained-batch')
+
+function AbstractLevelDOWN (location) {
+  if (!arguments.length || location === undefined)
+    throw new Error('constructor requires at least a location argument')
+
+  if (typeof location != 'string')
+    throw new Error('constructor requires a location string argument')
+
+  this.location = location
+}
+
+AbstractLevelDOWN.prototype.open = function (options, callback) {
+  if (typeof options == 'function')
+    callback = options
+
+  if (typeof callback != 'function')
+    throw new Error('open() requires a callback argument')
+
+  if (typeof options != 'object')
+    options = {}
+
+  if (typeof this._open == 'function')
+    return this._open(options, callback)
+
+  process.nextTick(callback)
+}
+
+AbstractLevelDOWN.prototype.close = function (callback) {
+  if (typeof callback != 'function')
+    throw new Error('close() requires a callback argument')
+
+  if (typeof this._close == 'function')
+    return this._close(callback)
+
+  process.nextTick(callback)
+}
+
+AbstractLevelDOWN.prototype.get = function (key, options, callback) {
+  var err
+
+  if (typeof options == 'function')
+    callback = options
+
+  if (typeof callback != 'function')
+    throw new Error('get() requires a callback argument')
+
+  if (err = this._checkKeyValue(key, 'key', this._isBuffer))
+    return callback(err)
+
+  if (!this._isBuffer(key))
+    key = String(key)
+
+  if (typeof options != 'object')
+    options = {}
+
+  if (typeof this._get == 'function')
+    return this._get(key, options, callback)
+
+  process.nextTick(function () { callback(new Error('NotFound')) })
+}
+
+AbstractLevelDOWN.prototype.put = function (key, value, options, callback) {
+  var err
+
+  if (typeof options == 'function')
+    callback = options
+
+  if (typeof callback != 'function')
+    throw new Error('put() requires a callback argument')
+
+  if (err = this._checkKeyValue(key, 'key', this._isBuffer))
+    return callback(err)
+
+  if (err = this._checkKeyValue(value, 'value', this._isBuffer))
+    return callback(err)
+
+  if (!this._isBuffer(key))
+    key = String(key)
+
+  // coerce value to string in node, don't touch it in browser
+  // (indexeddb can store any JS type)
+  if (!this._isBuffer(value) && !process.browser)
+    value = String(value)
+
+  if (typeof options != 'object')
+    options = {}
+
+  if (typeof this._put == 'function')
+    return this._put(key, value, options, callback)
+
+  process.nextTick(callback)
+}
+
+AbstractLevelDOWN.prototype.del = function (key, options, callback) {
+  var err
+
+  if (typeof options == 'function')
+    callback = options
+
+  if (typeof callback != 'function')
+    throw new Error('del() requires a callback argument')
+
+  if (err = this._checkKeyValue(key, 'key', this._isBuffer))
+    return callback(err)
+
+  if (!this._isBuffer(key))
+    key = String(key)
+
+  if (typeof options != 'object')
+    options = {}
+
+  if (typeof this._del == 'function')
+    return this._del(key, options, callback)
+
+  process.nextTick(callback)
+}
+
+AbstractLevelDOWN.prototype.batch = function (array, options, callback) {
+  if (!arguments.length)
+    return this._chainedBatch()
+
+  if (typeof options == 'function')
+    callback = options
+
+  if (typeof callback != 'function')
+    throw new Error('batch(array) requires a callback argument')
+
+  if (!Array.isArray(array))
+    return callback(new Error('batch(array) requires an array argument'))
+
+  if (typeof options != 'object')
+    options = {}
+
+  var i = 0
+    , l = array.length
+    , e
+    , err
+
+  for (; i < l; i++) {
+    e = array[i]
+    if (typeof e != 'object')
+      continue
+
+    if (err = this._checkKeyValue(e.type, 'type', this._isBuffer))
+      return callback(err)
+
+    if (err = this._checkKeyValue(e.key, 'key', this._isBuffer))
+      return callback(err)
+
+    if (e.type == 'put') {
+      if (err = this._checkKeyValue(e.value, 'value', this._isBuffer))
+        return callback(err)
+    }
+  }
+
+  if (typeof this._batch == 'function')
+    return this._batch(array, options, callback)
+
+  process.nextTick(callback)
+}
+
+//TODO: remove from here, not a necessary primitive
+AbstractLevelDOWN.prototype.approximateSize = function (start, end, callback) {
+  if (   start == null
+      || end == null
+      || typeof start == 'function'
+      || typeof end == 'function') {
+    throw new Error('approximateSize() requires valid `start`, `end` and `callback` arguments')
+  }
+
+  if (typeof callback != 'function')
+    throw new Error('approximateSize() requires a callback argument')
+
+  if (!this._isBuffer(start))
+    start = String(start)
+
+  if (!this._isBuffer(end))
+    end = String(end)
+
+  if (typeof this._approximateSize == 'function')
+    return this._approximateSize(start, end, callback)
+
+  process.nextTick(function () {
+    callback(null, 0)
+  })
+}
+
+AbstractLevelDOWN.prototype._setupIteratorOptions = function (options) {
+  var self = this
+
+  options = xtend(options)
+
+  ;[ 'start', 'end', 'gt', 'gte', 'lt', 'lte' ].forEach(function (o) {
+    if (options[o] && self._isBuffer(options[o]) && options[o].length === 0)
+      delete options[o]
+  })
+
+  options.reverse = !!options.reverse
+
+  // fix `start` so it takes into account gt, gte, lt, lte as appropriate
+  if (options.reverse && options.lt)
+    options.start = options.lt
+  if (options.reverse && options.lte)
+    options.start = options.lte
+  if (!options.reverse && options.gt)
+    options.start = options.gt
+  if (!options.reverse && options.gte)
+    options.start = options.gte
+
+  if ((options.reverse && options.lt && !options.lte)
+    || (!options.reverse && options.gt && !options.gte))
+    options.exclusiveStart = true // start should *not* include matching key
+
+  return options
+}
+
+AbstractLevelDOWN.prototype.iterator = function (options) {
+  if (typeof options != 'object')
+    options = {}
+
+  options = this._setupIteratorOptions(options)
+
+  if (typeof this._iterator == 'function')
+    return this._iterator(options)
+
+  return new AbstractIterator(this)
+}
+
+AbstractLevelDOWN.prototype._chainedBatch = function () {
+  return new AbstractChainedBatch(this)
+}
+
+AbstractLevelDOWN.prototype._isBuffer = function (obj) {
+  return Buffer.isBuffer(obj)
+}
+
+AbstractLevelDOWN.prototype._checkKeyValue = function (obj, type) {
+  if (obj === null || obj === undefined)
+    return new Error(type + ' cannot be `null` or `undefined`')
+
+  if (obj === null || obj === undefined)
+    return new Error(type + ' cannot be `null` or `undefined`')
+
+  if (this._isBuffer(obj)) {
+    if (obj.length === 0)
+      return new Error(type + ' cannot be an empty Buffer')
+  } else if (String(obj) === '')
+    return new Error(type + ' cannot be an empty String')
+}
+
+module.exports.AbstractLevelDOWN    = AbstractLevelDOWN
+module.exports.AbstractIterator     = AbstractIterator
+module.exports.AbstractChainedBatch = AbstractChainedBatch
diff --git a/abstract/approximate-size-test.js b/abstract/approximate-size-test.js
new file mode 100644
index 0000000..b67d428
--- /dev/null
+++ b/abstract/approximate-size-test.js
@@ -0,0 +1,121 @@
+var db
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test argument-less approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db)
+      , { name: 'Error', message: 'approximateSize() requires valid `start`, `end` and `callback` arguments' }
+      , 'no-arg approximateSize() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 1-arg, approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db, 'foo')
+      , { name: 'Error', message: 'approximateSize() requires valid `start`, `end` and `callback` arguments' }
+      , 'callback-less, 1-arg approximateSize() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 2-arg, approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db, 'foo', 'bar')
+      , { name: 'Error', message: 'approximateSize() requires a callback argument' }
+      , 'callback-less, 2-arg approximateSize() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 3-arg, approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db, function () {})
+      , { name: 'Error', message: 'approximateSize() requires valid `start`, `end` and `callback` arguments' }
+      , 'callback-only approximateSize() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-only approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db, function () {})
+      , { name: 'Error', message: 'approximateSize() requires valid `start`, `end` and `callback` arguments' }
+      , 'callback-only approximateSize() throws'
+    )
+    t.end()
+  })
+
+  test('test 1-arg + callback approximateSize() throws', function (t) {
+    t.throws(
+        db.approximateSize.bind(db, 'foo', function () {})
+      , { name: 'Error', message: 'approximateSize() requires valid `start`, `end` and `callback` arguments' }
+      , '1-arg + callback approximateSize() throws'
+    )
+    t.end()
+  })
+}
+
+module.exports.approximateSize = function (test) {
+  test('test approximateSize()', function (t) {
+    var data = Array.apply(null, Array(10000)).map(function () {
+      return 'aaaaaaaaaa'
+    }).join('')
+
+    db.batch(
+        Array.apply(null, Array(10)).map(function (x, i) {
+          return { type: 'put', key: 'foo' + i, value: data }
+        })
+      , function (err) {
+          t.notOk(err, 'no error')
+
+          // cycle open/close to ensure a pack to .sst
+
+          db.close(function (err) {
+            t.notOk(err, 'no error')
+
+            db.open(function (err) {
+              t.notOk(err, 'no error')
+
+              db.approximateSize('!', '~', function (err, size) {
+                t.notOk(err, 'no error')
+
+                t.type(size, 'number')
+                t.ok(
+                    size > 40000 // account for snappy compression
+                                 // original would be ~100000
+                  , 'size reports a reasonable amount (' + size + ')'
+                )
+
+                db.close(function (err) {
+                  t.notOk(err, 'no error')
+                  t.end()
+                })
+              })
+            })
+          })
+        }
+    )
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.approximateSize(test)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/batch-test.js b/abstract/batch-test.js
new file mode 100644
index 0000000..4585840
--- /dev/null
+++ b/abstract/batch-test.js
@@ -0,0 +1,144 @@
+var db
+  , verifyNotFoundError = require('./util').verifyNotFoundError
+  , isTypedArray        = require('./util').isTypedArray
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test callback-less, 2-arg, batch() throws', function (t) {
+    t.throws(db.batch.bind(db, 'foo', {}), 'callback-less, 2-arg batch() throws')
+    t.end()
+  })
+
+  test('test batch() with missing `value`', function (t) {
+    db.batch([{ type: 'put', key: 'foo1' }], function (err) {
+      t.equal(err.message, 'value cannot be `null` or `undefined`', 'correct error message')
+      t.end()
+    })
+  })
+
+  test('test batch() with null `value`', function (t) {
+    db.batch([{ type: 'put', key: 'foo1', value: null }], function (err) {
+      t.equal(err.message, 'value cannot be `null` or `undefined`', 'correct error message')
+      t.end()
+    })
+  })
+
+  test('test batch() with missing `key`', function (t) {
+    db.batch([{ type: 'put', value: 'foo1' }], function (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      t.end()
+    })
+  })
+
+  test('test batch() with null `key`', function (t) {
+    db.batch([{ type: 'put', key: null, value: 'foo1' }], function (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      t.end()
+    })
+  })
+
+  test('test batch() with missing `key` and `value`', function (t) {
+    db.batch([{ type: 'put' }], function (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      t.end()
+    })
+  })
+}
+
+module.exports.batch = function (test) {
+  test('test batch() with empty array', function (t) {
+    db.batch([], function (err) {
+      t.notOk(err, 'no error')
+      t.end()
+    })
+  })
+
+  test('test simple batch()', function (t) {
+    db.batch([{ type: 'put', key: 'foo', value: 'bar' }], function (err) {
+      t.notOk(err, 'no error')
+
+      db.get('foo', function (err, value) {
+        t.notOk(err, 'no error')
+        var result
+        if (isTypedArray(value)) {
+          result = String.fromCharCode.apply(null, new Uint16Array(value))
+        } else {
+          t.ok(typeof Buffer != 'undefined' && value instanceof Buffer)
+          result = value.toString()
+        }
+        t.equal(result, 'bar')
+        t.end()
+      })
+    })
+  })
+
+  test('test multiple batch()', function (t) {
+    db.batch([
+        { type: 'put', key: 'foobatch1', value: 'bar1' }
+      , { type: 'put', key: 'foobatch2', value: 'bar2' }
+      , { type: 'put', key: 'foobatch3', value: 'bar3' }
+      , { type: 'del', key: 'foobatch2' }
+    ], function (err) {
+      t.notOk(err, 'no error')
+
+      var r = 0
+        , done = function () {
+            if (++r == 3)
+              t.end()
+          }
+
+      db.get('foobatch1', function (err, value) {
+        t.notOk(err, 'no error')
+        var result
+        if (isTypedArray(value)) {
+          result = String.fromCharCode.apply(null, new Uint16Array(value))
+        } else {
+          t.ok(typeof Buffer != 'undefined' && value instanceof Buffer)
+          result = value.toString()
+        }
+        t.equal(result, 'bar1')
+        done()
+      })
+
+      db.get('foobatch2', function (err, value) {
+        t.ok(err, 'entry not found')
+        t.ok(typeof value == 'undefined', 'value is undefined')
+        t.ok(verifyNotFoundError(err), 'NotFound error')
+        done()
+      })
+
+      db.get('foobatch3', function (err, value) {
+        t.notOk(err, 'no error')
+        var result
+        if (isTypedArray(value)) {
+          result = String.fromCharCode.apply(null, new Uint16Array(value))
+        } else {
+          t.ok(typeof Buffer != 'undefined' && value instanceof Buffer)
+          result = value.toString()
+        }
+        t.equal(result, 'bar3')
+        done()
+      })
+    })
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.batch(test)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/chained-batch-test.js b/abstract/chained-batch-test.js
new file mode 100644
index 0000000..b38a68d
--- /dev/null
+++ b/abstract/chained-batch-test.js
@@ -0,0 +1,222 @@
+var db
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test batch#put() with missing `value`', function (t) {
+    try {
+      db.batch().put('foo1')
+    } catch (err) {
+      t.equal(err.message, 'value cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#put() with null `value`', function (t) {
+    try {
+      db.batch().put('foo1', null)
+    } catch (err) {
+      t.equal(err.message, 'value cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#put() with missing `key`', function (t) {
+    try {
+      db.batch().put(undefined, 'foo1')
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#put() with null `key`', function (t) {
+    try {
+      db.batch().put(null, 'foo1')
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#put() with missing `key` and `value`', function (t) {
+    try {
+      db.batch().put()
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#del() with missing `key`', function (t) {
+    try {
+      db.batch().del()
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#del() with null `key`', function (t) {
+    try {
+      db.batch().del(null)
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#del() with null `key`', function (t) {
+    try {
+      db.batch().del(null)
+    } catch (err) {
+      t.equal(err.message, 'key cannot be `null` or `undefined`', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#clear() doesn\'t throw', function (t) {
+    db.batch().clear()
+    t.end()
+  })
+
+  test('test batch#write() with no callback', function (t) {
+    try {
+      db.batch().write()
+    } catch (err) {
+      t.equal(err.message, 'write() requires a callback argument', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#put() after write()', function (t) {
+    var batch = db.batch().put('foo', 'bar')
+    batch.write(function () {})
+    try {
+      batch.put('boom', 'bang')
+    } catch (err) {
+      t.equal(err.message, 'write() already called on this batch', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#del() after write()', function (t) {
+    var batch = db.batch().put('foo', 'bar')
+    batch.write(function () {})
+    try {
+      batch.del('foo')
+    } catch (err) {
+      t.equal(err.message, 'write() already called on this batch', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#clear() after write()', function (t) {
+    var batch = db.batch().put('foo', 'bar')
+    batch.write(function () {})
+    try {
+      batch.clear()
+    } catch (err) {
+      t.equal(err.message, 'write() already called on this batch', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+
+  test('test batch#write() after write()', function (t) {
+    var batch = db.batch().put('foo', 'bar')
+    batch.write(function () {})
+    try {
+      batch.write(function (err) {})
+    } catch (err) {
+      t.equal(err.message, 'write() already called on this batch', 'correct error message')
+      return t.end()
+    }
+    t.fail('should have thrown')
+    t.end()
+  })
+}
+
+module.exports.batch = function (test, testCommon) {
+  test('test basic batch', function (t) {
+    db.batch(
+        [
+            { type: 'put', key: 'one', value: '1' }
+          , { type: 'put', key: 'two', value: '2' }
+          , { type: 'put', key: 'three', value: '3' }
+        ]
+      , function (err) {
+          t.notOk(err, 'no error')
+
+          db.batch()
+            .put('1', 'one')
+            .del('2', 'two')
+            .put('3', 'three')
+            .clear()
+            .put('one', 'I')
+            .put('two', 'II')
+            .del('three')
+            .put('foo', 'bar')
+            .write(function (err) {
+              t.notOk(err, 'no error')
+              testCommon.collectEntries(
+                  db.iterator({ keyAsBuffer: false, valueAsBuffer: false })
+                , function (err, data) {
+                    t.notOk(err, 'no error')
+                    t.equal(data.length, 3, 'correct number of entries')
+                    var expected = [
+                        { key: 'foo', value: 'bar' }
+                      , { key: 'one', value: 'I' }
+                      , { key: 'two', value: 'II' }
+                    ]
+                    t.deepEqual(data, expected)
+                    t.end()
+                  }
+              )
+            })
+        }
+    )
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.batch(test, testCommon)
+  module.exports.tearDown(test, testCommon)
+}
\ No newline at end of file
diff --git a/abstract/close-test.js b/abstract/close-test.js
new file mode 100644
index 0000000..7492c49
--- /dev/null
+++ b/abstract/close-test.js
@@ -0,0 +1,24 @@
+module.exports.close = function (leveldown, test, testCommon) {
+  test('test close()', function (t) {
+    var db = leveldown(testCommon.location())
+
+    db.open(function (err) {
+      t.notOk(err, 'no error')
+      t.throws(
+          db.close.bind(db)
+        , { name: 'Error', message: 'close() requires a callback argument' }
+        , 'no-arg close() throws'
+      )
+      t.throws(
+          db.close.bind(db, 'foo')
+        , { name: 'Error', message: 'close() requires a callback argument' }
+        , 'non-callback close() throws'
+      )
+
+      db.close(function (err) {
+        t.notOk(err, 'no error')
+        t.end()
+      })
+    })
+  })
+}
\ No newline at end of file
diff --git a/abstract/del-test.js b/abstract/del-test.js
new file mode 100644
index 0000000..8a70e7d
--- /dev/null
+++ b/abstract/del-test.js
@@ -0,0 +1,77 @@
+var db
+  , verifyNotFoundError = require('./util').verifyNotFoundError
+  , isTypedArray        = require('./util').isTypedArray
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test argument-less del() throws', function (t) {
+    t.throws(
+        db.del.bind(db)
+      , { name: 'Error', message: 'del() requires a callback argument' }
+      , 'no-arg del() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 1-arg, del() throws', function (t) {
+    t.throws(
+        db.del.bind(db, 'foo')
+      , { name: 'Error', message: 'del() requires a callback argument' }
+      , 'callback-less, 1-arg del() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 3-arg, del() throws', function (t) {
+    t.throws(
+        db.del.bind(db, 'foo', {})
+      , { name: 'Error', message: 'del() requires a callback argument' }
+      , 'callback-less, 2-arg del() throws'
+    )
+    t.end()
+  })
+}
+
+module.exports.del = function (test) {
+  test('test simple del()', function (t) {
+    db.put('foo', 'bar', function (err) {
+      t.notOk(err, 'no error')
+      db.del('foo', function (err) {
+        t.notOk(err, 'no error')
+        db.get('foo', function (err) {
+          t.ok(err, 'entry propertly deleted')
+          t.ok(typeof value == 'undefined', 'value is undefined')
+          t.ok(verifyNotFoundError(err), 'NotFound error')
+          t.end()
+        })
+      })
+    })
+  })
+
+  test('test del on non-existent key', function (t) {
+    db.del('blargh', function (err) {
+      t.notOk(err, 'should not error on delete')
+      t.end()
+    })
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.del(test)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/get-test.js b/abstract/get-test.js
new file mode 100644
index 0000000..2b865f0
--- /dev/null
+++ b/abstract/get-test.js
@@ -0,0 +1,125 @@
+var db
+  , verifyNotFoundError = require('./util').verifyNotFoundError
+  , isTypedArray        = require('./util').isTypedArray
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test argument-less get() throws', function (t) {
+    t.throws(
+        db.get.bind(db)
+      , { name: 'Error', message: 'get() requires a callback argument' }
+      , 'no-arg get() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 1-arg, get() throws', function (t) {
+    t.throws(
+        db.get.bind(db, 'foo')
+      , { name: 'Error', message: 'get() requires a callback argument' }
+      , 'callback-less, 1-arg get() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 3-arg, get() throws', function (t) {
+    t.throws(
+        db.get.bind(db, 'foo', {})
+      , { name: 'Error', message: 'get() requires a callback argument' }
+      , 'callback-less, 2-arg get() throws'
+    )
+    t.end()
+  })
+}
+
+module.exports.get = function (test) {
+  test('test simple get()', function (t) {
+    db.put('foo', 'bar', function (err) {
+      t.notOk(err, 'no error')
+      db.get('foo', function (err, value) {
+        t.notOk(err, 'no error')
+        t.ok(typeof value !== 'string', 'should not be string by default')
+
+        var result
+        if (isTypedArray(value)) {
+          result = String.fromCharCode.apply(null, new Uint16Array(value))
+        } else {
+          t.ok(typeof Buffer != 'undefined' && value instanceof Buffer)
+          result = value.toString()
+        }
+
+        t.equal(result, 'bar')
+
+        db.get('foo', {}, function (err, value) { // same but with {}
+          t.notOk(err, 'no error')
+          t.ok(typeof value !== 'string', 'should not be string by default')
+
+          var result
+          if (isTypedArray(value)) {
+            result = String.fromCharCode.apply(null, new Uint16Array(value))
+          } else {
+            t.ok(typeof Buffer != 'undefined' && value instanceof Buffer)
+            result = value.toString()
+          }
+
+          t.equal(result, 'bar')
+
+          db.get('foo', { asBuffer: false }, function (err, value) {
+            t.notOk(err, 'no error')
+            t.ok(typeof value === 'string', 'should be string if not buffer')
+            t.equal(value, 'bar')
+            t.end()
+          })
+        })
+      })
+    })
+  })
+
+  test('test simultaniously get()', function (t) {
+    db.put('hello', 'world', function (err) {
+      t.notOk(err, 'should not error')
+      var r = 0
+        , done = function () {
+            if (++r == 20)
+              t.end()
+          }
+        , i = 0
+        , j = 0
+
+      for (; i < 10; ++i)
+        db.get('hello', function(err, value) {
+          t.notOk(err, 'should not error')
+          t.equal(value.toString(), 'world')
+          done()
+        })
+
+      for (; j < 10; ++j)
+        db.get('not found', function(err, value) {
+          t.ok(err, 'should error')
+          t.ok(verifyNotFoundError(err), 'should have correct error message')
+          t.ok(typeof value == 'undefined', 'value is undefined')
+          done()
+        })
+    })
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.get(test)
+  module.exports.tearDown(test, testCommon)
+}
\ No newline at end of file
diff --git a/abstract/iterator-test.js b/abstract/iterator-test.js
new file mode 100644
index 0000000..ec85904
--- /dev/null
+++ b/abstract/iterator-test.js
@@ -0,0 +1,460 @@
+var db
+  , sourceData = (function () {
+      var d = []
+        , i = 0
+        , k
+      for (; i < 100; i++) {
+        k = (i < 10 ? '0' : '') + i
+        d.push({
+            type  : 'put'
+          , key   : k
+          , value : Math.random()
+        })
+      }
+      return d
+    }())
+  , transformSource = function (d) {
+      return { key: d.key, value: String(d.value) }
+    }
+
+module.exports.sourceData      = sourceData
+module.exports.transformSource = transformSource
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test argument-less iterator#next() throws', function (t) {
+    var iterator = db.iterator()
+    t.throws(
+        iterator.next.bind(iterator)
+      , { name: 'Error', message: 'next() requires a callback argument' }
+      , 'no-arg iterator#next() throws'
+    )
+    iterator.end(t.end.bind(t))
+  })
+
+  test('test argument-less iterator#end() after next() throws', function (t) {
+    var iterator = db.iterator()
+    iterator.next(function () {
+      t.throws(
+          iterator.end.bind(iterator)
+        , { name: 'Error', message: 'end() requires a callback argument' }
+        , 'no-arg iterator#end() throws'
+      )
+      iterator.end(t.end.bind(t))
+    })
+  })
+
+  test('test argument-less iterator#end() throws', function (t) {
+    var iterator = db.iterator()
+    t.throws(
+        iterator.end.bind(iterator)
+      , { name: 'Error', message: 'end() requires a callback argument' }
+      , 'no-arg iterator#end() throws'
+    )
+    iterator.end(t.end.bind(t))
+  })
+}
+
+module.exports.sequence = function (test) {
+  test('test twice iterator#end() callback with error', function (t) {
+    var iterator = db.iterator()
+    iterator.end(function (err) {
+      t.notOk(err, 'no error')
+      iterator.end(function(err2) {
+        t.ok(err2, 'returned error')
+        t.equal(err2.name, 'Error', 'correct error')
+        t.equal(err2.message, 'end() already called on iterator')
+        t.end()
+      })
+    })
+  })
+
+  test('test iterator#next after iterator#end() callback with error', function (t) {
+    var iterator = db.iterator()
+    iterator.end(function (err) {
+      t.notOk(err, 'no error')
+      iterator.next(function(err2) {
+        t.ok(err2, 'returned error')
+        t.equal(err2.name, 'Error', 'correct error')
+        t.equal(err2.message, 'cannot call next() after end()', 'correct message')
+        t.end()
+      })
+    })
+  })
+
+  test('test twice iterator#next() throws', function (t) {
+    var iterator = db.iterator()
+    iterator.next(function (err) {
+      t.notOk(err, 'no error')
+      iterator.end(function (err) {
+        t.notOk(err, 'no error')
+        t.end()
+      })
+    })
+
+    iterator.next(function(err) {
+      t.ok(err, 'returned error')
+      t.equal(err.name, 'Error', 'correct error')
+      t.equal(err.message, 'cannot call next() before previous next() has completed')
+    })
+  })
+}
+
+module.exports.iterator = function (leveldown, test, testCommon, collectEntries) {
+  test('test simple iterator()', function (t) {
+    var data = [
+            { type: 'put', key: 'foobatch1', value: 'bar1' }
+          , { type: 'put', key: 'foobatch2', value: 'bar2' }
+          , { type: 'put', key: 'foobatch3', value: 'bar3' }
+        ]
+      , idx = 0
+
+    db.batch(data, function (err) {
+      t.notOk(err, 'no error')
+      var iterator = db.iterator()
+        , fn = function (err, key, value) {
+            t.notOk(err, 'no error')
+            if (key && value) {
+              t.equal(key.toString(), data[idx].key, 'correct key')
+              t.equal(value.toString(), data[idx].value, 'correct value')
+              process.nextTick(next)
+              idx++
+            } else { // end
+              t.ok(typeof err === 'undefined', 'err argument is undefined')
+              t.ok(typeof key === 'undefined', 'key argument is undefined')
+              t.ok(typeof value === 'undefined', 'value argument is undefined')
+              t.equal(idx, data.length, 'correct number of entries')
+              iterator.end(function () {
+                t.end()
+              })
+            }
+          }
+        , next = function () {
+            iterator.next(fn)
+          }
+
+      next()
+    })
+  })
+
+  /** the following tests are mirroring the same series of tests in
+    * LevelUP read-stream-test.js
+    */
+
+  test('setUp #2', function (t) {
+    db.close(function () {
+      db = leveldown(testCommon.location())
+      db.open(function () {
+        db.batch(sourceData, t.end.bind(t))
+      })
+    })
+  })
+
+  test('test full data collection', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.slice().reverse().map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=0', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '00' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=50', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=50 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '50', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key (49.5)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '49.5' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key (49999)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '49999' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '49.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end=50', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, end: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key (50.5)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, end: '50.5' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key (50555)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, end: '50555' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, end: '50.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 49, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  // end='0', starting key is actually '00' so it should avoid it
+  test('test iterator with end=0', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, end: '0' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '30', end: '70' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice(30, 71).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '70', end: '30', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(29, 70).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 20 }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20 and start=20', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '20', limit: 20 }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(20, 40).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 20, reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20 and start=20 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '79', limit: 20, reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(20, 40).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  // the default limit value from levelup is -1
+  test('test iterator with limit=-1 should iterate over whole database', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: -1}), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end after limit', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 20, end: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end before limit', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 50, end: '19' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start after database end', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '9a' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with start after database end and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, start: '9a', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.slice().reverse().map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start and end after database and and reverse=true', function (t) {
+    collectEntries(db.iterator({ start: '9b', end: '9a', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  function testIteratorCollectsFullDatabase (name, iteratorOptions) {
+    iteratorOptions.keyAsBuffer   = false
+    iteratorOptions.valueAsBuffer = false
+    test(name, function (t) {
+      collectEntries(db.iterator(iteratorOptions), function (err, data) {
+        t.notOk(err, 'no error')
+        t.equal(data.length, 100, 'correct number of entries')
+        var expected = sourceData.map(transformSource)
+        t.deepEqual(data, expected)
+        t.end()
+      })
+    })
+  }
+  if (!process.browser) {
+    // Can't use buffers as query keys in indexeddb (I think :P)
+    testIteratorCollectsFullDatabase(
+        'test iterator with start as empty buffer'
+      , { start: new Buffer(0) }
+    )
+    testIteratorCollectsFullDatabase(
+        'test iterator with end as empty buffer'
+      , { end: new Buffer(0) }
+    )
+  }
+  testIteratorCollectsFullDatabase(
+      'test iterator with start as empty string'
+    , { start: '' }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with start as null'
+    , { start: null }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with end as empty string'
+    , { end: '' }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with end as null'
+    , { end: null }
+  )
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.sequence(test)
+  module.exports.iterator(leveldown, test, testCommon, testCommon.collectEntries)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/leveldown-test.js b/abstract/leveldown-test.js
new file mode 100644
index 0000000..205122b
--- /dev/null
+++ b/abstract/leveldown-test.js
@@ -0,0 +1,17 @@
+module.exports.args = function (leveldown, test) {
+  test('test database creation no-arg throws', function (t) {
+    t.throws(
+        leveldown
+      , { name: 'Error', message: 'constructor requires at least a location argument' }
+      , 'no-arg leveldown() throws'
+    )
+    t.end()
+  })
+
+  test('test database open no-arg throws', function (t) {
+    var db = leveldown('foo')
+    t.ok(db, 'database object returned')
+    t.ok(typeof db.open === 'function', 'open() function exists')
+    t.end()
+  })
+}
\ No newline at end of file
diff --git a/abstract/open-test.js b/abstract/open-test.js
new file mode 100644
index 0000000..5bbeeb0
--- /dev/null
+++ b/abstract/open-test.js
@@ -0,0 +1,96 @@
+module.exports.setUp = function (test, testCommon) {
+  test('setUp', testCommon.setUp)
+}
+
+module.exports.args = function (leveldown, test, testCommon) {
+  test('test database open no-arg throws', function (t) {
+    var db = leveldown(testCommon.location())
+    t.throws(
+        db.open.bind(db)
+      , { name: 'Error', message: 'open() requires a callback argument' }
+      , 'no-arg open() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 1-arg, open() throws', function (t) {
+    var db = leveldown(testCommon.location())
+    t.throws(
+        db.open.bind(db, {})
+      , { name: 'Error', message: 'open() requires a callback argument' }
+      , 'callback-less, 1-arg open() throws'
+    )
+    t.end()
+  })
+}
+
+module.exports.open = function (leveldown, test, testCommon) {
+  test('test database open, no options', function (t) {
+    var db = leveldown(testCommon.location())
+
+    // default createIfMissing=true, errorIfExists=false
+    db.open(function (err) {
+        t.notOk(err, 'no error')
+        db.close(function () {
+          t.end()
+        })
+      })
+  })
+
+  test('test database open, options and callback', function (t) {
+    var db = leveldown(testCommon.location())
+
+    // default createIfMissing=true, errorIfExists=false
+    db.open({}, function (err) {
+        t.notOk(err, 'no error')
+        db.close(function () {
+          t.end()
+        })
+      })
+  })
+}
+
+module.exports.openAdvanced = function (leveldown, test, testCommon) {
+  test('test database open createIfMissing:false', function (t) {
+    var db = leveldown(testCommon.location())
+
+    db.open({ createIfMissing: false }, function (err) {
+      t.ok(err, 'error')
+      t.ok(/does not exist/.test(err.message), 'error is about dir not existing')
+      t.end()
+    })
+  })
+
+  test('test database open errorIfExists:true', function (t) {
+    var location = testCommon.location()
+      , db       = leveldown(location)
+
+    // make a valid database first, then close and dispose
+    db.open({}, function (err) {
+      t.notOk(err, 'no error')
+      db.close(function (err) {
+        t.notOk(err, 'no error')
+
+        // open again with 'errorIfExists'
+        db = leveldown(location)
+        db.open({ createIfMissing: false, errorIfExists: true }, function (err) {
+          t.ok(err, 'error')
+          t.ok(/exists/.test(err.message), 'error is about already existing')
+          t.end()
+        })
+      })
+    })
+  })
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', testCommon.tearDown)
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(test, testCommon)
+  module.exports.args(leveldown, test, testCommon)
+  module.exports.open(leveldown, test, testCommon)
+  module.exports.openAdvanced(leveldown, test, testCommon)
+  module.exports.tearDown(test, testCommon)
+}
\ No newline at end of file
diff --git a/abstract/put-get-del-test.js b/abstract/put-get-del-test.js
new file mode 100644
index 0000000..e7e0935
--- /dev/null
+++ b/abstract/put-get-del-test.js
@@ -0,0 +1,167 @@
+/**** SETUP & UTILITY STUFF ****/
+
+
+var db
+  , testBuffer
+  , test
+  , verifyNotFoundError = require('./util').verifyNotFoundError
+  , isTypedArray        = require('./util').isTypedArray
+
+function makeGetDelErrorTests (type, key, expectedError) {
+  test('test get() with ' + type + ' causes error', function (t) {
+    db.get(key, function (err) {
+      t.ok(err, 'has error')
+      t.ok(err instanceof Error)
+      t.ok(err.message.match(expectedError), 'correct error message')
+      t.end()
+    })
+  })
+
+  test('test del() with ' + type + ' causes error', function (t) {
+    db.del(key, function (err) {
+      t.ok(err, 'has error')
+      t.ok(err instanceof Error)
+      t.ok(err.message.match(expectedError), 'correct error message')
+      t.end()
+    })
+  })
+}
+
+function makePutErrorTest (type, key, value, expectedError) {
+  test('test put() with ' + type + ' causes error', function (t) {
+    db.put(key, value, function (err) {
+      t.ok(err, 'has error')
+      t.ok(err instanceof Error)
+      t.ok(err.message.match(expectedError), 'correct error message')
+      t.end()
+    })
+  })
+}
+
+function makePutGetDelSuccessfulTest (type, key, value) {
+  test('test put()/get()/del() with ' + type, function (t) {
+    db.put(key, value, function (err) {
+      t.notOk(err, 'no error')
+      db.get(key, function (err, _value) {
+        t.notOk(err, 'no error, has key/value for `' + key + '`')
+        t.ok(Buffer.isBuffer(_value), 'is a Buffer')
+        var result = _value.toString()
+        value = value.toString()
+        t.equals(result, value)
+        db.del(key, function (err) {
+          t.notOk(err, 'no error, deleted key/value for `' + key + '`')
+          db.get(key, function (err,  value) {
+            t.ok(err, 'entry propertly deleted')
+            t.ok(verifyNotFoundError(err), 'should have correct error message')
+            t.ok(typeof value == 'undefined', 'value is undefined')
+            t.end()
+          })
+        })
+      })
+    })
+  })
+}
+
+function makeErrorKeyTest (type, key, expectedError) {
+  makeGetDelErrorTests(type, key, expectedError)
+  makePutErrorTest(type, key, 'foo', expectedError)
+}
+
+/**** SETUP ENVIRONMENT ****/
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+/**** TEST ERROR KEYS ****/
+
+module.exports.errorKeys = function (testFunc, BufferType) {
+  if (!BufferType) BufferType = Buffer
+  test = testFunc
+  makeErrorKeyTest('null key', null, /key cannot be `null` or `undefined`/)
+  makeErrorKeyTest('undefined key', undefined, /key cannot be `null` or `undefined`/)
+  makeErrorKeyTest('empty String key', '', /key cannot be an empty String/)
+  makeErrorKeyTest('empty Buffer key', new BufferType(0), /key cannot be an empty \w*Buffer/)
+  makeErrorKeyTest('empty Array key', [], /key cannot be an empty String/)
+}
+
+/**** TEST NON-ERROR KEYS ****/
+
+module.exports.nonErrorKeys = function (testFunc) {
+  // valid falsey keys
+  test = testFunc
+  makePutGetDelSuccessfulTest('`false` key', false, 'foo false')
+  makePutGetDelSuccessfulTest('`0` key', 0, 'foo 0')
+  makePutGetDelSuccessfulTest('`NaN` key', NaN, 'foo NaN')
+
+  // standard String key
+  makePutGetDelSuccessfulTest(
+      'long String key'
+    , 'some long string that I\'m using as a key for this unit test, cross your fingers dude, we\'re going in!'
+    , 'foo'
+  )
+
+  if (!process.browser) {
+    // Buffer key
+    makePutGetDelSuccessfulTest('Buffer key', testBuffer, 'foo')
+  }
+
+  // non-empty Array as a value
+  makePutGetDelSuccessfulTest('Array value', 'foo', [1,2,3,4])
+}
+
+/**** TEST ERROR VALUES ****/
+
+module.exports.errorValues = function (testFunc, BufferType) {
+  if (!BufferType) BufferType = Buffer
+  test = testFunc
+  makePutErrorTest('null value', 'foo', null, /value cannot be `null` or `undefined`/)
+  makePutErrorTest('undefined value', 'foo', undefined, /value cannot be `null` or `undefined`/)
+  makePutErrorTest('empty String value', 'foo', '', /value cannot be an empty String/)
+  makePutErrorTest('empty Buffer value', 'foo', new BufferType(0), /value cannot be an empty \w*Buffer/)
+  makePutErrorTest('empty Array value', 'foo', [], /value cannot be an empty String/)
+}
+
+module.exports.nonErrorKeys = function (testFunc) {
+  // valid falsey values
+  test = testFunc
+  makePutGetDelSuccessfulTest('`false` value', 'foo false', false)
+  makePutGetDelSuccessfulTest('`0` value', 'foo 0', 0)
+  makePutGetDelSuccessfulTest('`NaN` value', 'foo NaN', NaN)
+
+  // standard String value
+  makePutGetDelSuccessfulTest(
+      'long String value'
+    , 'foo'
+    , 'some long string that I\'m using as a key for this unit test, cross your fingers dude, we\'re going in!'
+  )
+
+  // standard Buffer value
+  makePutGetDelSuccessfulTest('Buffer value', 'foo', testBuffer)
+
+  // non-empty Array as a key
+  makePutGetDelSuccessfulTest('Array key', [1,2,3,4], 'foo')
+}
+
+/**** CLEANUP ENVIRONMENT ****/
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, testFunc, testCommon, buffer, BufferType) {
+  testBuffer = buffer
+  test = testFunc
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.errorKeys(test, BufferType)
+  module.exports.nonErrorKeys(test)
+  module.exports.errorValues(test, BufferType)
+  module.exports.nonErrorKeys(test)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/put-test.js b/abstract/put-test.js
new file mode 100644
index 0000000..55558ee
--- /dev/null
+++ b/abstract/put-test.js
@@ -0,0 +1,92 @@
+var db
+  , verifyNotFoundError = require('./util').verifyNotFoundError
+  , isTypedArray        = require('./util').isTypedArray
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.args = function (test) {
+  test('test argument-less put() throws', function (t) {
+    t.throws(
+        db.put.bind(db)
+      , { name: 'Error', message: 'put() requires a callback argument' }
+      , 'no-arg put() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 1-arg, put() throws', function (t) {
+    t.throws(
+        db.put.bind(db, 'foo')
+      , { name: 'Error', message: 'put() requires a callback argument' }
+      , 'callback-less, 1-arg put() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 2-arg, put() throws', function (t) {
+    t.throws(
+        db.put.bind(db, 'foo', 'bar')
+      , { name: 'Error', message: 'put() requires a callback argument' }
+      , 'callback-less, 2-arg put() throws'
+    )
+    t.end()
+  })
+
+  test('test callback-less, 3-arg, put() throws', function (t) {
+    t.throws(
+        db.put.bind(db, 'foo', 'bar', {})
+      , { name: 'Error', message: 'put() requires a callback argument' }
+      , 'callback-less, 3-arg put() throws'
+    )
+    t.end()
+  })
+}
+
+module.exports.put = function (test) {
+  test('test simple put()', function (t) {
+    db.put('foo', 'bar', function (err) {
+      t.notOk(err, 'no error')
+      db.get('foo', function (err, value) {
+        t.notOk(err, 'no error')
+        var result = value.toString()
+        if (isTypedArray(value))
+          result = String.fromCharCode.apply(null, new Uint16Array(value))
+        t.equal(result, 'bar')
+        t.end()
+      })
+    })
+  })
+  
+  if (process.browser) {
+    test('test object value put()', function (t) {
+      db.put('dood', {pete: 'sampras'}, function (err) {
+        t.notOk(err, 'no error')
+        db.get('dood', { asBuffer: false }, function (err, value) {
+          t.notOk(err, 'no error')
+          t.equal(JSON.stringify(value), JSON.stringify({pete: 'sampras'}))
+          t.end()
+        })
+      })
+    })
+  }
+
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.args(test)
+  module.exports.put(test)
+  module.exports.tearDown(test, testCommon)
+}
\ No newline at end of file
diff --git a/abstract/ranges-test.js b/abstract/ranges-test.js
new file mode 100644
index 0000000..9b8e1da
--- /dev/null
+++ b/abstract/ranges-test.js
@@ -0,0 +1,435 @@
+var db
+  , sourceData      = require('./iterator-test').sourceData
+  , transformSource = require('./iterator-test').transformSource
+
+module.exports.setUp = function (leveldown, test, testCommon) {
+  test('setUp common', testCommon.setUp)
+  test('setUp db', function (t) {
+    db = leveldown(testCommon.location())
+    db.open(t.end.bind(t))
+  })
+}
+
+module.exports.iterator = function (leveldown, test, testCommon, collectEntries) {
+  test('test simple iterator()', function (t) {
+    var data = [
+            { type: 'put', key: 'foobatch1', value: 'bar1' }
+          , { type: 'put', key: 'foobatch2', value: 'bar2' }
+          , { type: 'put', key: 'foobatch3', value: 'bar3' }
+        ]
+      , idx = 0
+
+    db.batch(data, function (err) {
+      t.notOk(err, 'no error')
+      var iterator = db.iterator()
+        , fn = function (err, key, value) {
+            t.notOk(err, 'no error')
+            if (key && value) {
+              t.equal(key.toString(), data[idx].key, 'correct key')
+              t.equal(value.toString(), data[idx].value, 'correct value')
+              process.nextTick(next)
+              idx++
+            } else { // end
+              t.ok(typeof err === 'undefined', 'err argument is undefined')
+              t.ok(typeof key === 'undefined', 'key argument is undefined')
+              t.ok(typeof value === 'undefined', 'value argument is undefined')
+              t.equal(idx, data.length, 'correct number of entries')
+              iterator.end(function () {
+                t.end()
+              })
+            }
+          }
+        , next = function () {
+            iterator.next(fn)
+          }
+
+      next()
+    })
+  })
+
+  /** the following tests are mirroring the same series of tests in
+    * LevelUP read-stream-test.js
+    */
+
+  test('setUp #2', function (t) {
+    db.close(function () {
+      db = leveldown(testCommon.location())
+      db.open(function () {
+        db.batch(sourceData, t.end.bind(t))
+      })
+    })
+  })
+
+  test('test full data collection', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.slice().reverse().map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with gte=0', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '00' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with gte=50', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with lte=50 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '50', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key (49.5)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '49.5' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key (49999)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '49999' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '49.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lt: '49.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lt: '50', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 50, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(50).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end=50', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key (50.5)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '50.5' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key (50555)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '50555' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key (50555)', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lt: '50555' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 51, 'correct number of entries')
+      var expected = sourceData.slice(0, 51).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end being a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '50.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 49, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with gt a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gt: '50.5', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 49, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with gt a midway key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gt: '50', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 49, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with gt 50 key and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gt: '50', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 49, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(0, 49).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  // end='0', starting key is actually '00' so it should avoid it
+  test('test iterator with end=0', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '0' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  // end='0', starting key is actually '00' so it should avoid it
+  test('test iterator with end<0', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lt: '0' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '30', lte: '70' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice(30, 71).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gt: '29', lt: '71' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice(30, 71).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '70', gte: '30', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(29, 70).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start=30 and end=70 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lt: '71', gt: '29', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 41, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(29, 70).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20 and start=20', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '20', limit: 20 }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(20, 40).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with limit=20 and start=79 and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '79', limit: 20, reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice().reverse().slice(20, 40).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end after limit', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 20, lte: '50' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with end before limit', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, limit: 50, lte: '19' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 20, 'correct number of entries')
+      var expected = sourceData.slice(0, 20).map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start after database end', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gte: '9a' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with start after database end', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, gt: '9a' }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with start after database end and reverse=true', function (t) {
+    collectEntries(db.iterator({ keyAsBuffer: false, valueAsBuffer: false, lte: '9a', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, sourceData.length, 'correct number of entries')
+      var expected = sourceData.slice().reverse().map(transformSource)
+      t.deepEqual(data, expected)
+      t.end()
+    })
+  })
+
+  test('test iterator with start and end after database and and reverse=true', function (t) {
+    collectEntries(db.iterator({ lte: '9b', gte: '9a', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  test('test iterator with lt and gt after database and and reverse=true', function (t) {
+    collectEntries(db.iterator({ lt: '9b', gt: '9a', reverse: true }), function (err, data) {
+      t.notOk(err, 'no error')
+      t.equal(data.length, 0, 'correct number of entries')
+      t.end()
+    })
+  })
+
+  function testIteratorCollectsFullDatabase (name, iteratorOptions) {
+    iteratorOptions.keyAsBuffer   = false
+    iteratorOptions.valueAsBuffer = false
+    test(name, function (t) {
+      collectEntries(db.iterator(iteratorOptions), function (err, data) {
+        t.notOk(err, 'no error')
+        t.equal(data.length, 100, 'correct number of entries')
+        var expected = sourceData.map(transformSource)
+        t.deepEqual(data, expected)
+        t.end()
+      })
+    })
+  }
+  if (!process.browser) {
+    // Can't use buffers as query keys in indexeddb (I think :P)
+    testIteratorCollectsFullDatabase(
+        'test iterator with start as empty buffer'
+      , { start: new Buffer(0) }
+    )
+    testIteratorCollectsFullDatabase(
+        'test iterator with end as empty buffer'
+      , { end: new Buffer(0) }
+    )
+  }
+  testIteratorCollectsFullDatabase(
+      'test iterator with start as empty string'
+    , { gte: '' }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with start as null'
+    , { gte: null }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with end as empty string'
+    , { lte: '' }
+  )
+  testIteratorCollectsFullDatabase(
+      'test iterator with end as null'
+    , { lte: null }
+  )
+}
+
+module.exports.tearDown = function (test, testCommon) {
+  test('tearDown', function (t) {
+    db.close(testCommon.tearDown.bind(null, t))
+  })
+}
+
+module.exports.all = function (leveldown, test, testCommon) {
+  module.exports.setUp(leveldown, test, testCommon)
+  module.exports.iterator(leveldown, test, testCommon, testCommon.collectEntries)
+  module.exports.tearDown(test, testCommon)
+}
diff --git a/abstract/util.js b/abstract/util.js
new file mode 100644
index 0000000..82f155e
--- /dev/null
+++ b/abstract/util.js
@@ -0,0 +1,8 @@
+module.exports.verifyNotFoundError = function verifyNotFoundError (err) {
+  return (/NotFound/i).test(err.message)
+}
+
+module.exports.isTypedArray = function isTypedArray (value) {
+  return (typeof ArrayBuffer != 'undefined' && value instanceof ArrayBuffer)
+      || (typeof Uint8Array != 'undefined' && value instanceof Uint8Array)
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d123c5a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,46 @@
+{
+  "name": "abstract-leveldown",
+  "description": "An abstract prototype matching the LevelDOWN API",
+  "version": "0.12.3",
+  "contributors": [
+    "Rod Vagg <r at va.gg> (https://github.com/rvagg)",
+    "John Chesley <john at chesl.es> (https://github.com/chesles/)",
+    "Jake Verbaten <raynos2 at gmail.com> (https://github.com/raynos)",
+    "Dominic Tarr <dominic.tarr at gmail.com> (https://github.com/dominictarr)",
+    "Max Ogden <max at maxogden.com> (https://github.com/maxogden)",
+    "Lars-Magnus Skog <lars.magnus.skog at gmail.com> (https://github.com/ralphtheninja)",
+    "David Björklund <david.bjorklund at gmail.com> (https://github.com/kesla)",
+    "Julian Gruber <julian at juliangruber.com> (https://github.com/juliangruber)",
+    "Paolo Fragomeni <paolo at async.ly> (https://github.com/hij1nx)",
+    "Anton Whalley <anton.whalley at nearform.com> (https://github.com/No9)",
+    "Matteo Collina <matteo.collina at gmail.com> (https://github.com/mcollina)",
+    "Pedro Teixeira <pedro.teixeira at gmail.com> (https://github.com/pgte)",
+    "James Halliday <mail at substack.net> (https://github.com/substack)"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/rvagg/node-abstract-leveldown.git"
+  },
+  "homepage": "https://github.com/rvagg/node-abstract-leveldown",
+  "keywords": [
+    "leveldb",
+    "leveldown",
+    "levelup"
+  ],
+  "main": "./abstract-leveldown.js",
+  "dependencies": {
+    "xtend": "~3.0.0"
+  },
+  "devDependencies": {
+    "tap": "*",
+    "sinon": "*",
+    "rimraf": "*"
+  },
+  "browser": {
+    "rimraf": false
+  },
+  "scripts": {
+    "test": "node ./test.js"
+  },
+  "license": "MIT"
+}
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..4ec2870
--- /dev/null
+++ b/test.js
@@ -0,0 +1,559 @@
+const tap                  = require('tap')
+    , sinon                = require('sinon')
+    , util                 = require('util')
+    , testCommon           = require('./testCommon')
+    , AbstractLevelDOWN    = require('./').AbstractLevelDOWN
+    , AbstractIterator     = require('./').AbstractIterator
+    , AbstractChainedBatch = require('./').AbstractChainedBatch
+
+function factory (location) {
+  return new AbstractLevelDOWN(location)
+}
+
+/*** compatibility with basic LevelDOWN API ***/
+
+require('./abstract/leveldown-test').args(factory, tap.test, testCommon)
+
+require('./abstract/open-test').args(factory, tap.test, testCommon)
+
+require('./abstract/del-test').setUp(factory, tap.test, testCommon)
+require('./abstract/del-test').args(tap.test)
+
+require('./abstract/get-test').setUp(factory, tap.test, testCommon)
+require('./abstract/get-test').args(tap.test)
+
+require('./abstract/put-test').setUp(factory, tap.test, testCommon)
+require('./abstract/put-test').args(tap.test)
+
+require('./abstract/put-get-del-test').setUp(factory, tap.test, testCommon)
+require('./abstract/put-get-del-test').errorKeys(tap.test)
+//require('./abstract/put-get-del-test').nonErrorKeys(tap.test, testCommon)
+require('./abstract/put-get-del-test').errorValues(tap.test)
+//require('./abstract/test/put-get-del-test').nonErrorKeys(tap.test, testCommon)
+require('./abstract/put-get-del-test').tearDown(tap.test, testCommon)
+
+require('./abstract/approximate-size-test').setUp(factory, tap.test, testCommon)
+require('./abstract/approximate-size-test').args(tap.test)
+
+require('./abstract/batch-test').setUp(factory, tap.test, testCommon)
+require('./abstract/batch-test').args(tap.test)
+
+require('./abstract/chained-batch-test').setUp(factory, tap.test, testCommon)
+require('./abstract/chained-batch-test').args(tap.test)
+
+require('./abstract/close-test').close(factory, tap.test, testCommon)
+
+require('./abstract/iterator-test').setUp(factory, tap.test, testCommon)
+require('./abstract/iterator-test').args(tap.test)
+require('./abstract/iterator-test').sequence(tap.test)
+
+/*** extensibility ***/
+
+tap.test('test core extensibility', function (t) {
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+    t.equal(this.location, location, 'location set on `this`')
+    t.end()
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  ;new Test('foobar')
+})
+
+tap.test('test open() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._open = spy
+
+  test = new Test('foobar')
+  test.open(expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _open() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _open() was correct')
+  t.equal(spy.getCall(0).args.length, 2, 'got two arguments')
+  t.deepEqual(spy.getCall(0).args[0], {}, 'got blank options argument')
+  t.equal(spy.getCall(0).args[1], expectedCb, 'got expected cb argument')
+
+  test.open(expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _open() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _open() was correct')
+  t.equal(spy.getCall(1).args.length, 2, 'got two arguments')
+  t.deepEqual(spy.getCall(1).args[0], expectedOptions, 'got blank options argument')
+  t.equal(spy.getCall(1).args[1], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test close() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._close = spy
+
+  test = new Test('foobar')
+  test.close(expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _close() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _close() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one arguments')
+  t.equal(spy.getCall(0).args[0], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test get() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , expectedKey = 'a key'
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._get = spy
+
+  test = new Test('foobar')
+  test.get(expectedKey, expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _get() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _get() was correct')
+  t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
+  t.deepEqual(spy.getCall(0).args[1], {}, 'got blank options argument')
+  t.equal(spy.getCall(0).args[2], expectedCb, 'got expected cb argument')
+
+  test.get(expectedKey, expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _get() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _get() was correct')
+  t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
+  t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got blank options argument')
+  t.equal(spy.getCall(1).args[2], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test del() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , expectedKey = 'a key'
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._del = spy
+
+  test = new Test('foobar')
+  test.del(expectedKey, expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _del() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _del() was correct')
+  t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
+  t.deepEqual(spy.getCall(0).args[1], {}, 'got blank options argument')
+  t.equal(spy.getCall(0).args[2], expectedCb, 'got expected cb argument')
+
+  test.del(expectedKey, expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _del() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _del() was correct')
+  t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
+  t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got blank options argument')
+  t.equal(spy.getCall(1).args[2], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test put() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , expectedKey = 'a key'
+    , expectedValue = 'a value'
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._put = spy
+
+  test = new Test('foobar')
+  test.put(expectedKey, expectedValue, expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _put() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _put() was correct')
+  t.equal(spy.getCall(0).args.length, 4, 'got four arguments')
+  t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
+  t.equal(spy.getCall(0).args[1], expectedValue, 'got expected value argument')
+  t.deepEqual(spy.getCall(0).args[2], {}, 'got blank options argument')
+  t.equal(spy.getCall(0).args[3], expectedCb, 'got expected cb argument')
+
+  test.put(expectedKey, expectedValue, expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _put() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _put() was correct')
+  t.equal(spy.getCall(1).args.length, 4, 'got four arguments')
+  t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
+  t.equal(spy.getCall(1).args[1], expectedValue, 'got expected value argument')
+  t.deepEqual(spy.getCall(1).args[2], expectedOptions, 'got blank options argument')
+  t.equal(spy.getCall(1).args[3], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test approximateSize() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedStart = 'a start'
+    , expectedEnd = 'an end'
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._approximateSize = spy
+
+  test = new Test('foobar')
+  test.approximateSize(expectedStart, expectedEnd, expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _approximateSize() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _approximateSize() was correct')
+  t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(0).args[0], expectedStart, 'got expected start argument')
+  t.equal(spy.getCall(0).args[1], expectedEnd, 'got expected end argument')
+  t.equal(spy.getCall(0).args[2], expectedCb, 'got expected cb argument')
+  t.end()
+})
+
+tap.test('test batch() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , expectedArray = [ 1, 2 ]
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._batch = spy
+
+  test = new Test('foobar')
+
+  test.batch(expectedArray, expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _batch() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _batch() was correct')
+  t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(0).args[0], expectedArray, 'got expected array argument')
+  t.deepEqual(spy.getCall(0).args[1], {}, 'got expected options argument')
+  t.equal(spy.getCall(0).args[2], expectedCb, 'got expected callback argument')
+
+  test.batch(expectedArray, expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _batch() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _batch() was correct')
+  t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(1).args[0], expectedArray, 'got expected array argument')
+  t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
+  t.equal(spy.getCall(1).args[2], expectedCb, 'got expected callback argument')
+
+  t.end()
+})
+
+tap.test('test chained batch() (array) extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , expectedOptions = { options: 1 }
+    , expectedArray = [ 1, 2 ]
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._batch = spy
+
+  test = new Test('foobar')
+
+  test.batch().put('foo', 'bar').del('bang').write(expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _batch() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _batch() was correct')
+  t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(0).args[0].length, 2, 'got expected array argument')
+  t.deepEqual(spy.getCall(0).args[0][0], { type: 'put', key: 'foo', value: 'bar' }, 'got expected array argument[0]')
+  t.deepEqual(spy.getCall(0).args[0][1], { type: 'del', key: 'bang' }, 'got expected array argument[1]')
+  t.deepEqual(spy.getCall(0).args[1], {}, 'got expected options argument')
+  t.equal(spy.getCall(0).args[2], expectedCb, 'got expected callback argument')
+
+  test.batch().put('foo', 'bar').del('bang').write(expectedOptions, expectedCb)
+
+  t.equal(spy.callCount, 2, 'got _batch() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _batch() was correct')
+  t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
+  t.equal(spy.getCall(1).args[0].length, 2, 'got expected array argument')
+  t.deepEqual(spy.getCall(1).args[0][0], { type: 'put', key: 'foo', value: 'bar' }, 'got expected array argument[0]')
+  t.deepEqual(spy.getCall(1).args[0][1], { type: 'del', key: 'bang' }, 'got expected array argument[1]')
+  t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
+  t.equal(spy.getCall(1).args[2], expectedCb, 'got expected callback argument')
+
+  t.end()
+})
+
+tap.test('test chained batch() (custom _chainedBatch) extensibility', function (t) {
+  var spy = sinon.spy()
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._chainedBatch = spy
+
+  test = new Test('foobar')
+
+  test.batch()
+
+  t.equal(spy.callCount, 1, 'got _chainedBatch() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _chainedBatch() was correct')
+
+  test.batch()
+
+  t.equal(spy.callCount, 2, 'got _chainedBatch() call')
+  t.equal(spy.getCall(1).thisValue, test, '`this` on _chainedBatch() was correct')
+
+  t.end()
+})
+
+tap.test('test AbstractChainedBatch extensibility', function (t) {
+  function Test (db) {
+    AbstractChainedBatch.call(this, db)
+    t.equal(this._db, db, 'db set on `this`')
+    t.end()
+  }
+
+  util.inherits(Test, AbstractChainedBatch)
+
+  new Test('foobar')
+})
+
+tap.test('test write() extensibility', function (t) {
+  var spy = sinon.spy()
+    , spycb = sinon.spy()
+    , test
+
+  function Test (db) {
+    AbstractChainedBatch.call(this, db)
+  }
+
+  util.inherits(Test, AbstractChainedBatch)
+
+  Test.prototype._write = spy
+
+  test = new Test('foobar')
+  test.write(spycb)
+
+  t.equal(spy.callCount, 1, 'got _write() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _write() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one argument')
+  // awkward here cause of nextTick & an internal wrapped cb
+  t.type(spy.getCall(0).args[0], 'function', 'got a callback function')
+  t.equal(spycb.callCount, 0, 'spycb not called')
+  spy.getCall(0).args[0]()
+  t.equal(spycb.callCount, 1, 'spycb called, i.e. was our cb argument')
+  t.end()
+})
+
+tap.test('test put() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedKey = 'key'
+    , expectedValue = 'value'
+    , returnValue
+    , test
+
+  function Test (db) {
+    AbstractChainedBatch.call(this, db)
+  }
+
+  util.inherits(Test, AbstractChainedBatch)
+
+  Test.prototype._put = spy
+
+  test = new Test(factory('foobar'))
+  returnValue = test.put(expectedKey, expectedValue)
+  t.equal(spy.callCount, 1, 'got _put call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _put() was correct')
+  t.equal(spy.getCall(0).args.length, 2, 'got two arguments')
+  t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
+  t.equal(spy.getCall(0).args[1], expectedValue, 'got expected value argument')
+  t.equal(returnValue, test, 'get expected return value')
+  t.end()
+})
+
+tap.test('test del() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedKey = 'key'
+    , returnValue
+    , test
+
+  function Test (db) {
+    AbstractChainedBatch.call(this, db)
+  }
+
+  util.inherits(Test, AbstractChainedBatch)
+
+  Test.prototype._del = spy
+
+  test = new Test(factory('foobar'))
+  returnValue = test.del(expectedKey)
+  t.equal(spy.callCount, 1, 'got _del call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _del() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one argument')
+  t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
+  t.equal(returnValue, test, 'get expected return value')
+  t.end()
+})
+
+tap.test('test clear() extensibility', function (t) {
+  var spy = sinon.spy()
+    , returnValue
+    , test
+
+  function Test (db) {
+    AbstractChainedBatch.call(this, db)
+  }
+
+  util.inherits(Test, AbstractChainedBatch)
+
+  Test.prototype._clear = spy
+
+  test = new Test(factory('foobar'))
+  returnValue = test.clear()
+  t.equal(spy.callCount, 1, 'got _clear call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _clear() was correct')
+  t.equal(spy.getCall(0).args.length, 0, 'got zero arguments')
+  t.equal(returnValue, test, 'get expected return value')
+  t.end()
+})
+
+tap.test('test iterator() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedOptions = { options: 1, reverse: false } // reverse now explicitly set
+    , test
+
+  function Test (location) {
+    AbstractLevelDOWN.call(this, location)
+  }
+
+  util.inherits(Test, AbstractLevelDOWN)
+
+  Test.prototype._iterator = spy
+
+  test = new Test('foobar')
+  test.iterator({ options: 1 })
+
+  t.equal(spy.callCount, 1, 'got _close() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _close() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one arguments')
+  t.deepEqual(spy.getCall(0).args[0], expectedOptions, 'got expected options argument')
+  t.end()
+})
+
+tap.test('test AbstractIterator extensibility', function (t) {
+  function Test (db) {
+    AbstractIterator.call(this, db)
+    t.equal(this.db, db, 'db set on `this`')
+    t.end()
+  }
+
+  util.inherits(Test, AbstractIterator)
+
+  ;new Test('foobar')
+})
+
+tap.test('test next() extensibility', function (t) {
+  var spy = sinon.spy()
+    , spycb = sinon.spy()
+    , test
+
+  function Test (db) {
+    AbstractIterator.call(this, db)
+  }
+
+  util.inherits(Test, AbstractIterator)
+
+  Test.prototype._next = spy
+
+  test = new Test('foobar')
+  test.next(spycb)
+
+  t.equal(spy.callCount, 1, 'got _next() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _next() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one arguments')
+  // awkward here cause of nextTick & an internal wrapped cb
+  t.type(spy.getCall(0).args[0], 'function', 'got a callback function')
+  t.equal(spycb.callCount, 0, 'spycb not called')
+  spy.getCall(0).args[0]()
+  t.equal(spycb.callCount, 1, 'spycb called, i.e. was our cb argument')
+  t.end()
+})
+
+tap.test('test end() extensibility', function (t) {
+  var spy = sinon.spy()
+    , expectedCb = function () {}
+    , test
+
+  function Test (db) {
+    AbstractIterator.call(this, db)
+  }
+
+  util.inherits(Test, AbstractIterator)
+
+  Test.prototype._end = spy
+
+  test = new Test('foobar')
+  test.end(expectedCb)
+
+  t.equal(spy.callCount, 1, 'got _end() call')
+  t.equal(spy.getCall(0).thisValue, test, '`this` on _end() was correct')
+  t.equal(spy.getCall(0).args.length, 1, 'got one arguments')
+  t.equal(spy.getCall(0).args[0], expectedCb, 'got expected cb argument')
+  t.end()
+})
diff --git a/testCommon.js b/testCommon.js
new file mode 100644
index 0000000..3f9d29d
--- /dev/null
+++ b/testCommon.js
@@ -0,0 +1,75 @@
+var path      = require('path')
+  , fs        = !process.browser && require('fs')
+  , rimraf    = !process.browser && require('rimraf')
+
+var dbidx = 0
+
+  , location = function () {
+      return path.join(__dirname, '_leveldown_test_db_' + dbidx++)
+    }
+
+  , lastLocation = function () {
+      return path.join(__dirname, '_leveldown_test_db_' + dbidx)
+    }
+
+  , cleanup = function (callback) {
+      if (process.browser)
+        return callback()
+
+      fs.readdir(__dirname, function (err, list) {
+        if (err) return callback(err)
+
+        list = list.filter(function (f) {
+          return (/^_leveldown_test_db_/).test(f)
+        })
+
+        if (!list.length)
+          return callback()
+
+        var ret = 0
+
+        list.forEach(function (f) {
+          rimraf(path.join(__dirname, f), function (err) {
+            if (++ret == list.length)
+              callback()
+          })
+        })
+      })
+    }
+
+  , setUp = function (t) {
+      cleanup(function (err) {
+        t.notOk(err, 'cleanup returned an error')
+        t.end()
+      })
+    }
+
+  , tearDown = function (t) {
+      setUp(t) // same cleanup!
+    }
+
+  , collectEntries = function (iterator, callback) {
+      var data = []
+        , next = function () {
+            iterator.next(function (err, key, value) {
+              if (err) return callback(err)
+              if (!arguments.length) {
+                return iterator.end(function (err) {
+                  callback(err, data)
+                })
+              }
+              data.push({ key: key, value: value })
+              process.nextTick(next)
+            })
+          }
+      next()
+    }
+
+module.exports = {
+    location       : location
+  , cleanup        : cleanup
+  , lastLocation   : lastLocation
+  , setUp          : setUp
+  , tearDown       : tearDown
+  , collectEntries : collectEntries
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-abstract-leveldown.git



More information about the Pkg-javascript-commits mailing list