diff --git a/checkers/check_crc32c.c b/checkers/check_crc32c.c new file mode 100644 index 0000000..4eca8f1 --- /dev/null +++ b/checkers/check_crc32c.c @@ -0,0 +1,9 @@ +#include + +int main(int argc, char** argv) +{ + char* buffer; + size_t size; + ::crc32c::Extend(0, buffer, size); + return 0; +} diff --git a/tests/compileme.c b/checkers/check_fdatasync.c similarity index 98% rename from tests/compileme.c rename to checkers/check_fdatasync.c index d756ad7..63d1ed1 100644 --- a/tests/compileme.c +++ b/checkers/check_fdatasync.c @@ -5,4 +5,3 @@ int main(int argc, char** argv) fdatasync(-1); return 0; } - diff --git a/tests/compileme2.c b/checkers/check_fullfsync.c similarity index 98% rename from tests/compileme2.c rename to checkers/check_fullfsync.c index e44c0ab..7632eed 100644 --- a/tests/compileme2.c +++ b/checkers/check_fullfsync.c @@ -5,4 +5,3 @@ int main(int argc, char** argv) int i = F_FULLSYNC; return 0; } - diff --git a/tests/compileme3.c b/checkers/check_ocloexec.c similarity index 98% rename from tests/compileme3.c rename to checkers/check_ocloexec.c index e4d5988..c719871 100644 --- a/tests/compileme3.c +++ b/checkers/check_ocloexec.c @@ -5,4 +5,3 @@ int main(int argc, char** argv) int i = O_CLOEXEC; return 0; } - diff --git a/checkers/check_snappy.c b/checkers/check_snappy.c new file mode 100644 index 0000000..21b8111 --- /dev/null +++ b/checkers/check_snappy.c @@ -0,0 +1,10 @@ +#include + +int main(int argc, char** argv) +{ + char* input; + size_t length; + size_t result; + snappy::GetUncompressedLength(input, length, &result); + return 0; +} diff --git a/checkers/check_zstd.c b/checkers/check_zstd.c new file mode 100644 index 0000000..837cdb0 --- /dev/null +++ b/checkers/check_zstd.c @@ -0,0 +1,8 @@ +#include + +int main(int argc, char** argv) +{ + size_t length; + ZSTD_compressBound(length); + return 0; +} diff --git a/leveldbstatic/prelude.nim b/leveldbstatic/prelude.nim index 82223b5..377685c 100644 --- a/leveldbstatic/prelude.nim +++ b/leveldbstatic/prelude.nim @@ -3,6 +3,7 @@ import os const root = currentSourcePath.parentDir.parentDir const envWindows = root/"vendor"/"util"/"env_windows.cc" const envPosix = root/"vendor"/"util"/"env_posix.cc" +const checkers = root/"checkers" when defined(windows): {.compile: envWindows.} @@ -15,11 +16,18 @@ when defined(posix): {.passc: "-DLEVELDB_PLATFORM_POSIX".} -{.passc: "-DHAVE_FDATASYNC=0".} -{.passc: "-DHAVE_FULLFSYNC=0".} -{.passc: "-DHAVE_O_CLOEXEC=0".} -{.passc: "-DHAVE_CRC32C=0".} -{.passc: "-DHAVE_SNAPPY=0".} -{.passc: "-DHAVE_ZSTD=0".} -{.passc: "-DHAVE_Zstd=0".} +static: + proc doesCompile(cfile: string): int = + let rv = gorgeEx("gcc " & cfile) + if rv[1] == 0: + return 1 + return 0 + + {.passc: "-DHAVE_FDATASYNC=" & $doesCompile(checkers/"check_fdatasync.c").} + {.passc: "-DHAVE_FULLFSYNC=" & $doesCompile(checkers/"check_fullfsync.c").} + {.passc: "-DHAVE_O_CLOEXEC=" & $doesCompile(checkers/"check_ocloexec.c").} + {.passc: "-DHAVE_CRC32C=" & $doesCompile(checkers/"check_crc32c.c").} + {.passc: "-DHAVE_SNAPPY=" & $doesCompile(checkers/"check_snappy.c").} + {.passc: "-DHAVE_ZSTD=" & $doesCompile(checkers/"check_zstd.c").} + {.passc: "-DHAVE_Zstd=" & $doesCompile(checkers/"check_zstd.c").} diff --git a/tests/test.nim b/tests/test.nim index 438a408..858e993 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -2,38 +2,374 @@ import unittest, options, os, osproc, sequtils, strutils import leveldbstatic as leveldb import leveldbstatic/raw -suite "checking": - test "A!": - echo "A!" +const + tmpDir = getTempDir() / "testleveldb" + tmpNimbleDir = tmpDir / "nimble" + tmpDbDir = tmpDir / "testdb" - when sizeof(int) == 8: - echo "int = 8" - else: - echo "int is not 8" +template cd*(dir: string, body: untyped) = + ## Sets the current dir to ``dir``, executes ``body`` and restores the + ## previous working dir. + block: + let lastDir = getCurrentDir() + setCurrentDir(dir) + body + setCurrentDir(lastDir) - when defined(fdatasync): - echo "yes fdatasync" - else: - echo "no fdatasync" +proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = + var quotedArgs = @args + quotedArgs.insert("-y") + quotedArgs.insert("--nimbleDir:" & tmpNimbleDir) + quotedArgs.insert("nimble") + quotedArgs = quotedArgs.map(proc (x: string): string = "\"" & x & "\"") - when defined(F_FULLFSYNC): - echo "yes full" - else: - echo "no full" + let cmd = quotedArgs.join(" ") + result = execCmdEx(cmd) + checkpoint(cmd) + checkpoint(result.output) - when defined(O_CLOEXEC): - echo "yes oclo" - else: - echo "no oclo" +proc execTool(args: varargs[string]): tuple[output: string, exitCode: int] = + var quotedArgs = @args + quotedArgs.insert(tmpDbDir) + quotedArgs.insert("--database") + quotedArgs.insert(findExe(tmpNimbleDir / "bin" / "leveldbtool")) + quotedArgs = quotedArgs.map(proc (x: string): string = "\"" & x & "\"") - static: - proc doesCompile(cfile: string): bool = - # static: - let rv = gorgeEx("gcc " & cfile) - echo rv[0] - return rv[1] == 0 + if not dirExists(tmpDbDir): + createDir(tmpDbDir) - echo "compileme.c: " & $doesCompile("compileme.c") - echo "compileme2.c: " & $doesCompile("compileme2.c") - echo "compileme3.c: " & $doesCompile("compileme3.c") + let cmd = quotedArgs.join(" ") + result = execCmdEx(cmd) + checkpoint(cmd) + checkpoint(result.output) +suite "leveldb": + + setup: + let env = leveldb_create_default_env() + let dbName = $(leveldb_env_get_test_directory(env)) + let db = leveldb.open(dbName) + + teardown: + db.close() + removeDb(dbName) + + test "version": + let (major, minor) = getLibVersion() + check(major > 0) + check(minor > 0) + + test "get nothing": + check(db.get("nothing") == none(string)) + + test "put and get": + db.put("hello", "world") + check(db.get("hello") == some("world")) + + test "get or default": + check(db.getOrDefault("nothing", "yes") == "yes") + + test "delete": + db.put("hello", "world") + db.delete("hello") + check(db.get("hello") == none(string)) + + test "get value with 0x00": + db.put("\0key", "\0ff") + check(db.get("\0key") == some("\0ff")) + + test "get empty value": + db.put("a", "") + check(db.get("a") == some("")) + + test "get empty key": + db.put("", "a") + check(db.get("") == some("a")) + + proc initData(db: LevelDb) = + db.put("aa", "1") + db.put("ba", "2") + db.put("bb", "3") + + test "iter": + initData(db) + check(toSeq(db.iter()) == @[("aa", "1"), ("ba", "2"), ("bb", "3")]) + + test "iter reverse": + initData(db) + check(toSeq(db.iter(reverse = true)) == + @[("bb", "3"), ("ba", "2"), ("aa", "1")]) + + test "iter seek": + initData(db) + check(toSeq(db.iter(seek = "ab")) == + @[("ba", "2"), ("bb", "3")]) + + test "iter seek reverse": + initData(db) + check(toSeq(db.iter(seek = "ab", reverse = true)) == + @[("ba", "2"), ("aa", "1")]) + + test "iter prefix": + initData(db) + check(toSeq(db.iterPrefix(prefix = "b")) == + @[("ba", "2"), ("bb", "3")]) + + test "iter range": + initData(db) + check(toSeq(db.iterRange(start = "a", limit = "ba")) == + @[("aa", "1"), ("ba", "2")]) + + test "iter range reverse": + initData(db) + check(toSeq(db.iterRange(start = "bb", limit = "b")) == + @[("bb", "3"), ("ba", "2")]) + + test "iter with 0x00": + db.put("\0z1", "\0ff") + db.put("z2\0", "ff\0") + check(toSeq(db.iter()) == @[("\0z1", "\0ff"), ("z2\0", "ff\0")]) + + test "iter empty value": + db.put("a", "") + check(toSeq(db.iter()) == @[("a", "")]) + + test "iter empty key": + db.put("", "a") + check(toSeq(db.iter()) == @[("", "a")]) + + test "repair database": + initData(db) + db.close() + repairDb(dbName) + + test "batch": + db.put("a", "1") + db.put("b", "2") + let batch = newBatch() + batch.put("a", "10") + batch.put("c", "30") + batch.delete("b") + db.write(batch) + check(toSeq(db.iter()) == @[("a", "10"), ("c", "30")]) + + test "batch append": + let batch = newBatch() + let batch2 = newBatch() + batch.put("a", "1") + batch2.put("b", "2") + batch2.delete("a") + batch.append(batch2) + db.write(batch) + check(toSeq(db.iter()) == @[("b", "2")]) + + test "batch clear": + let batch = newBatch() + batch.put("a", "1") + batch.clear() + batch.put("b", "2") + db.write(batch) + check(toSeq(db.iter()) == @[("b", "2")]) + + test "open with cache": + let ldb = leveldb.open(dbName & "-cache", cacheCapacity = 100000) + defer: + ldb.close() + removeDb(ldb.path) + ldb.put("a", "1") + check(toSeq(ldb.iter()) == @[("a", "1")]) + + test "open but no create": + expect LevelDbException: + let failed = leveldb.open(dbName & "-nocreate", create = false) + defer: + failed.close() + removeDb(failed.path) + + test "open but no reuse": + let old = leveldb.open(dbName & "-noreuse", reuse = true) + defer: + old.close() + removeDb(old.path) + + expect LevelDbException: + let failed = leveldb.open(old.path, reuse = false) + defer: + failed.close() + removeDb(failed.path) + + test "no compress": + db.close() + let nc = leveldb.open(dbName, compressionType = ctNoCompression) + defer: nc.close() + nc.put("a", "1") + check(toSeq(nc.iter()) == @[("a", "1")]) + +suite "leveldb queryIter": + + setup: + let env = leveldb_create_default_env() + let dbName = $(leveldb_env_get_test_directory(env)) + let db = leveldb.open(dbName) + let + k1 = "k1" + k2 = "k2" + k3 = "l3" + v1 = "v1" + v2 = "v2" + v3 = "v3" + empty = ("", "") + + db.put(k1, v1) + db.put(k2, v2) + db.put(k3, v3) + + teardown: + db.close() + removeDb(dbName) + + test "iterates all keys and values": + let iter = db.queryIter() + check: + not iter.finished + iter.next() == (k1, v1) + not iter.finished + iter.next() == (k2, v2) + not iter.finished + iter.next() == (k3, v3) + not iter.finished + iter.next() == empty + iter.finished + + test "skip": + let iter = db.queryIter(skip = 1) + check: + not iter.finished + iter.next() == (k2, v2) + not iter.finished + iter.next() == (k3, v3) + not iter.finished + iter.next() == empty + iter.finished + + test "limit": + let iter = db.queryIter(limit = 2) + check: + not iter.finished + iter.next() == (k1, v1) + not iter.finished + iter.next() == (k2, v2) + not iter.finished + iter.next() == empty + iter.finished + + test "iterates only keys": + let iter = db.queryIter(keysOnly = true) + check: + not iter.finished + iter.next() == (k1, "") + not iter.finished + iter.next() == (k2, "") + not iter.finished + iter.next() == (k3, "") + not iter.finished + iter.next() == empty + iter.finished + + test "iterates only 'k', both keys and values": + let iter = db.queryIter(prefix = "k") + check: + not iter.finished + iter.next() == (k1, v1) + not iter.finished + iter.next() == (k2, v2) + not iter.finished + iter.next() == empty + iter.finished + + test "iterates only 'k', skip": + let iter = db.queryIter(prefix = "k", skip = 1) + check: + not iter.finished + iter.next() == (k2, v2) + not iter.finished + iter.next() == empty + iter.finished + + test "iterate only 'k', limit": + let iter = db.queryIter(prefix = "k", limit = 1) + check: + not iter.finished + iter.next() == (k1, v1) + not iter.finished + iter.next() == empty + iter.finished + + test "iterates only 'k', only keys": + let iter = db.queryIter(prefix = "k", keysOnly = true) + check: + not iter.finished + iter.next() == (k1, "") + not iter.finished + iter.next() == (k2, "") + not iter.finished + iter.next() == empty + iter.finished + +suite "package": + + setup: + removeDir(tmpDir) + + test "import as package": + let (output, exitCode) = execNimble("install") + check exitCode == QuitSuccess + check output.contains("leveldbstatic installed successfully.") + + cd "tests"/"packagetest": + var (output, exitCode) = execNimble("build") + check exitCode == QuitSuccess + check output.contains("Building") + + (output, exitCode) = execCmdEx("./packagetest") + checkpoint output + check exitCode == QuitSuccess + check output.contains("leveldb works.") + +suite "tool": + + setup: + removeDir(tmpDir) + + test "leveldb tool": + var (output, exitCode) = execNimble("install") + check exitCode == QuitSuccess + check output.contains("Building") + + check execTool("-v").exitCode == QuitSuccess + check execTool("create").exitCode == QuitSuccess + check execTool("list").exitCode == QuitSuccess + + check execTool("put", "hello", "world").exitCode == QuitSuccess + (output, exitCode) = execTool("get", "hello") + check exitCode == QuitSuccess + check output == "world\L" + (output, exitCode) = execTool("list") + check exitCode == QuitSuccess + check output == "hello world\L" + + check execTool("delete", "hello").exitCode == QuitSuccess + (output, exitCode) = execTool("get", "hello") + check exitCode == QuitSuccess + check output == "" + (output, exitCode) = execTool("list") + check exitCode == QuitSuccess + check output == "" + + check execTool("put", "hello", "6130", "-x").exitCode == QuitSuccess + check execTool("get", "hello", "-x").output == "6130\L" + check execTool("get", "hello").output == "a0\L" + check execTool("list", "-x").output == "hello 6130\L" + check execTool("put", "hello", "0061", "-x").exitCode == QuitSuccess + check execTool("get", "hello", "-x").output == "0061\L" + check execTool("delete", "hello").exitCode == QuitSuccess