From 1ed442a68adb3378f063c7fe9ad71c8a01c6c630 Mon Sep 17 00:00:00 2001 From: mahlon <> Date: Tue, 18 Mar 2025 02:21:06 +0000 Subject: [PATCH] Start adding tests, using testament. FossilOrigin-Name: 6f368f0d303c65000c74f346b7bc39ffca964aff7767c60be2384739e5dc4d72 --- experiments/imdb/imdbimport.nim | 86 ++++++++++--------- kuzu.nimble | 7 ++ src/kuzu.nim | 9 +- src/kuzu/constants.nim | 4 +- .../t_can_create_new_connection.nim | 10 +++ .../t_can_interrupt_running_query.nim | 13 +++ .../t_can_set_connection_query_timeout.nim | 12 +++ tests/constants/t_has_a_default_config.nim | 6 ++ tests/constants/t_has_a_version.nim | 7 ++ tests/constants/t_knows_its_libversion.nim | 7 ++ .../constants/t_knows_its_storage_version.nim | 6 ++ tests/database/t_can_create_in_memory.nim | 7 ++ .../t_can_create_with_custom_config.nim | 17 ++++ .../t_creates_with_default_config.nim | 19 ++++ .../t_can_execute_a_simple_queries.nim | 20 +++++ tests/queries/t_raises_on_bad_syntax.nim | 14 +++ 16 files changed, 200 insertions(+), 44 deletions(-) create mode 100644 tests/connection/t_can_create_new_connection.nim create mode 100644 tests/connection/t_can_interrupt_running_query.nim create mode 100644 tests/connection/t_can_set_connection_query_timeout.nim create mode 100644 tests/constants/t_has_a_default_config.nim create mode 100644 tests/constants/t_has_a_version.nim create mode 100644 tests/constants/t_knows_its_libversion.nim create mode 100644 tests/constants/t_knows_its_storage_version.nim create mode 100644 tests/database/t_can_create_in_memory.nim create mode 100644 tests/database/t_can_create_with_custom_config.nim create mode 100644 tests/database/t_creates_with_default_config.nim create mode 100644 tests/queries/t_can_execute_a_simple_queries.nim create mode 100644 tests/queries/t_raises_on_bad_syntax.nim diff --git a/experiments/imdb/imdbimport.nim b/experiments/imdb/imdbimport.nim index 347c0b4..788873b 100644 --- a/experiments/imdb/imdbimport.nim +++ b/experiments/imdb/imdbimport.nim @@ -13,6 +13,7 @@ # See: https://developer.imdb.com/non-commercial-datasets/ import + std/dirs, std/os, std/sequtils, std/strformat, @@ -53,52 +54,56 @@ for file in FILES: var line = "" while tsv_stream.readLine( line ): - c += 1 - if c mod 1000 == 0: stderr.write( &"Parsing {file}... {c}\r" ) + try: + c += 1 + if c mod 1000 == 0: stderr.write( &"Parsing {file}... {c}\r" ) - var row = line.split( '\t' ) - case file + var row = line.split( '\t' ) + case file - # nconst primaryName birthYear deathYear primaryProfession knownForTitles - of "name.basics": - row = row[0..3] - row[0] = $row[0].replace( "nm" ).parseInt() + # nconst primaryName birthYear deathYear primaryProfession knownForTitles + of "name.basics": + row = row[0..3] + row[0] = $row[0].replace( "nm" ).parseInt() - # tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres - of "title.basics": - if row[1] != "movie": continue - row.delete( 1 ) - for i in 0..1: row.delete( 2 ) - row.delete( 3 ) - discard row.pop() - row[0] = $row[0].replace( "tt" ).parseInt() + # tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres + of "title.basics": + if row[1] != "movie": continue + row.delete( 1 ) + for i in 0..1: row.delete( 2 ) + row.delete( 3 ) + discard row.pop() + row[0] = $row[0].replace( "tt" ).parseInt() - # tconst ordering nconst category job characters - of "title.principals": - if row[3] != "actor" and row[3] != "actress": continue - row.delete( 1 ) - row = row[0..1] - row[0] = $row[0].replace( "tt" ).parseInt() - row[1] = $row[1].replace( "nm" ).parseInt() - row = @[ row[1], row[0] ] + # tconst ordering nconst category job characters + of "title.principals": + if row[3] != "actor" and row[3] != "actress": continue + row.delete( 1 ) + row = row[0..1] + row[0] = $row[0].replace( "tt" ).parseInt() + row[1] = $row[1].replace( "nm" ).parseInt() + row = @[ row[1], row[0] ] - if file.contains( ".basics" ): - row.applyIt( - # empty value / null - if it == "\\N": "" + if file.contains( ".basics" ): + row.applyIt( + # empty value / null + if it == "\\N": "" - # RFC 4180 escapes - elif it.contains( "\"" ) or it.contains( ',' ): - var value = it - value = value.replace( "\"", "\"\"" ) - value = value.replace( ",", "" ) - "\"" & value & "\"" + # RFC 4180 escapes + elif it.contains( "\"" ) or it.contains( ',' ): + var value = it + value = value.replace( "\"", "\"\"" ) + value = value.replace( ",", "" ) + "\"" & value & "\"" - else: it - ) + else: it + ) - csv_file.write( row.join(","), "\n" ) + csv_file.write( row.join(","), "\n" ) + + except ValueError: + continue tsv_stream.close() csv_file.close() @@ -109,10 +114,9 @@ for file in FILES: # Ok, now import into a fresh kuzu database. # -var db = newKuzuDatabase( DB ) -var conn = db.connect() - -if not DB.fileExists: +if not DB.dirExists: + var db = newKuzuDatabase( DB ) + var conn = db.connect() var duration = 0 for schema in @[ diff --git a/kuzu.nimble b/kuzu.nimble index 53c55f4..3c7dc77 100644 --- a/kuzu.nimble +++ b/kuzu.nimble @@ -15,3 +15,10 @@ requires "nim ^= 2.0.0" task makewrapper, "Generate the C wrapper using Futhark": exec "nim c -d:futharkWrap --outdir=. src/kuzu.nim" +task test, "Run the test suite.": + exec "testament all" + exec "testament html" + +task clean, "Remove all non-critical artifacts.": + exec "fossil clean --disable-undo --dotfiles --emptydirs -f -v" + diff --git a/src/kuzu.nim b/src/kuzu.nim index 02d9edb..b41779e 100644 --- a/src/kuzu.nim +++ b/src/kuzu.nim @@ -23,12 +23,19 @@ include "kuzu/connection.nim", "kuzu/queries.nim" + +proc kuzuVersionCompatible*(): bool = + ## Returns true if the system installed Kuzu library + ## is the expected version of this library wrapper. + result = KUZU_EXPECTED_LIBVERSION == KUZU_LIBVERSION + + when isMainModule: echo "Nim-Kuzu version: ", KUZU_VERSION, ". Expected library version: ", KUZU_EXPECTED_LIBVERSION, "." echo "Installed Kuzu library version ", KUZU_LIBVERSION, " (storage version ", KUZU_STORAGE_VERSION, ")" - if KUZU_EXPECTED_LIBVERSION == KUZU_LIBVERSION: + if kuzuVersionCompatible(): echo "Versions match!" else: echo "This library wraps a different version of Kuzu than what is installed." diff --git a/src/kuzu/constants.nim b/src/kuzu/constants.nim index c11ce8c..89587f2 100644 --- a/src/kuzu/constants.nim +++ b/src/kuzu/constants.nim @@ -3,8 +3,8 @@ const KUZU_VERSION* = "0.1.0" const KUZU_EXPECTED_LIBVERSION* = "0.8.2" -let KUZU_LIBVERSION* = kuzu_get_version() -let KUZU_STORAGE_VERSION* = kuzu_get_storage_version() +let KUZU_LIBVERSION* = $kuzu_get_version() +let KUZU_STORAGE_VERSION* = kuzu_get_storage_version().int let KUZU_DEFAULT_CONFIG* = kuzu_default_system_config() diff --git a/tests/connection/t_can_create_new_connection.nim b/tests/connection/t_can_create_new_connection.nim new file mode 100644 index 0000000..b52a2bc --- /dev/null +++ b/tests/connection/t_can_create_new_connection.nim @@ -0,0 +1,10 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + + +let db = newKuzuDatabase() + +assert db.path == "(in-memory)" +assert typeOf( db.connect ) is KuzuConnection + diff --git a/tests/connection/t_can_interrupt_running_query.nim b/tests/connection/t_can_interrupt_running_query.nim new file mode 100644 index 0000000..6e3e453 --- /dev/null +++ b/tests/connection/t_can_interrupt_running_query.nim @@ -0,0 +1,13 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + + +let db = newKuzuDatabase() +let conn = db.connect + +# FIXME: This test should really perform some +# long running query in a thread, and cancel +# it from elsewhere. +conn.queryInterrupt() + diff --git a/tests/connection/t_can_set_connection_query_timeout.nim b/tests/connection/t_can_set_connection_query_timeout.nim new file mode 100644 index 0000000..256d7a9 --- /dev/null +++ b/tests/connection/t_can_set_connection_query_timeout.nim @@ -0,0 +1,12 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +# There is currently no getter for this, so +# we'll just have to assume a lack of +# exception/error means success. +conn.queryTimeout( 1000 ) + diff --git a/tests/constants/t_has_a_default_config.nim b/tests/constants/t_has_a_default_config.nim new file mode 100644 index 0000000..db19f43 --- /dev/null +++ b/tests/constants/t_has_a_default_config.nim @@ -0,0 +1,6 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +assert typeOf( KUZU_DEFAULT_CONFIG ) is kuzu_system_config + diff --git a/tests/constants/t_has_a_version.nim b/tests/constants/t_has_a_version.nim new file mode 100644 index 0000000..7bb5f52 --- /dev/null +++ b/tests/constants/t_has_a_version.nim @@ -0,0 +1,7 @@ +# vim: set et sta sw=4 ts=4 : + +import re +import kuzu + +assert KUZU_VERSION.contains( re"^\d+\.\d+\.\d+$" ) + diff --git a/tests/constants/t_knows_its_libversion.nim b/tests/constants/t_knows_its_libversion.nim new file mode 100644 index 0000000..938312b --- /dev/null +++ b/tests/constants/t_knows_its_libversion.nim @@ -0,0 +1,7 @@ +# vim: set et sta sw=4 ts=4 : + +import re +import kuzu + +assert KUZU_LIBVERSION.contains( re"^\d+\.\d+\.\d+$" ) + diff --git a/tests/constants/t_knows_its_storage_version.nim b/tests/constants/t_knows_its_storage_version.nim new file mode 100644 index 0000000..31f500c --- /dev/null +++ b/tests/constants/t_knows_its_storage_version.nim @@ -0,0 +1,6 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +assert KUZU_STORAGE_VERSION >= 36 + diff --git a/tests/database/t_can_create_in_memory.nim b/tests/database/t_can_create_in_memory.nim new file mode 100644 index 0000000..ca96333 --- /dev/null +++ b/tests/database/t_can_create_in_memory.nim @@ -0,0 +1,7 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +var db = newKuzuDatabase() +assert db.path == "(in-memory)" + diff --git a/tests/database/t_can_create_with_custom_config.nim b/tests/database/t_can_create_with_custom_config.nim new file mode 100644 index 0000000..13d90fc --- /dev/null +++ b/tests/database/t_can_create_with_custom_config.nim @@ -0,0 +1,17 @@ +# vim: set et sta sw=4 ts=4 : + +import + std/dirs, + std/paths + +import kuzu + +const DATABASE_PATH = Path( "tmp/testdb" ) +DATABASE_PATH.removeDir() + +var db = newKuzuDatabase( $DATABASE_PATH, kuzuConfig( auto_checkpoint=false ) ) +assert db.path == "tmp/testdb" +assert db.config.auto_checkpoint == false + +DATABASE_PATH.removeDir() + diff --git a/tests/database/t_creates_with_default_config.nim b/tests/database/t_creates_with_default_config.nim new file mode 100644 index 0000000..b353679 --- /dev/null +++ b/tests/database/t_creates_with_default_config.nim @@ -0,0 +1,19 @@ +# vim: set et sta sw=4 ts=4 : + +import + std/dirs, + std/paths + +import kuzu + +const DATABASE_PATH = Path( "tmp/testdb" ) + +DATABASE_PATH.removeDir() +var db = newKuzuDatabase( $DATABASE_PATH ) + +assert db.path == $DATABASE_PATH +assert db.config == kuzuConfig() +assert db.config.read_only == false + +DATABASE_PATH.removeDir() + diff --git a/tests/queries/t_can_execute_a_simple_queries.nim b/tests/queries/t_can_execute_a_simple_queries.nim new file mode 100644 index 0000000..35edd0e --- /dev/null +++ b/tests/queries/t_can_execute_a_simple_queries.nim @@ -0,0 +1,20 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) +assert typeOf( q ) is KuzuQueryResult + +for thing in @[ "Camel", "Lampshade", "Delicious Cake" ]: + q = conn.query( "CREATE (d:Doop {thing: '" & thing & "'})" ) + assert typeOf( q ) is KuzuQueryResult + +q = conn.query( "MATCH (d:Doop) RETURN d.thing" ) +assert q.num_columns == 1 +assert q.num_tuples == 3 +assert q.compile_time > 0 +assert q.execution_time > 0 + diff --git a/tests/queries/t_raises_on_bad_syntax.nim b/tests/queries/t_raises_on_bad_syntax.nim new file mode 100644 index 0000000..8ad0ef2 --- /dev/null +++ b/tests/queries/t_raises_on_bad_syntax.nim @@ -0,0 +1,14 @@ +# vim: set et sta sw=4 ts=4 : + +import + std/re +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +try: + discard conn.query( "NOPE NOPE NOPE" ) +except KuzuQueryException as err: + assert err.msg.contains( re"""Error running query:.*extraneous input 'NOPE'""" ) +