@@ -1721,26 +1721,56 @@ function tests.test_db_stats_wa_fields()
17211721end
17221722
17231723function tests .test_init_finalize ()
1724- -- The library auto-initializes lazily, so finalize() then init() should
1725- -- succeed and leave the library usable for subsequent operations.
1726- tidesdb .finalize ()
1727- local rc = tidesdb .init (nil , nil , nil , nil )
1728- assert_eq (rc , 0 , " init after finalize should return 0 (freshly initialized)" )
1729- local rc2 = tidesdb .init (nil , nil , nil , nil )
1730- assert_eq (rc2 , - 1 , " second init should return -1 (already initialized)" )
1731-
1732- -- confirm the library still works after re-init
1733- local path = " ./test_db_init"
1734- cleanup_db (path )
1735- local db = tidesdb .TidesDB .open (path , { log_level = tidesdb .LogLevel .LOG_WARN })
1736- db :create_column_family (" c" )
1737- local cf = db :get_column_family (" c" )
1738- local txn = db :begin_txn ()
1739- txn :put (cf , " k" , " v" )
1740- txn :commit ()
1741- txn :free ()
1742- db :close ()
1743- cleanup_db (path )
1724+ -- tidesdb.init installs custom allocator functions (malloc/calloc/realloc/free)
1725+ -- and must run before any other TidesDB call; passing nil keeps the system
1726+ -- allocators. As in the C API it is honored only as the first initialization
1727+ -- and returns -1 once the library is already initialized, because the
1728+ -- allocators cannot be swapped under a live library. init/finalize mutate
1729+ -- process-global state, so we must NOT call them in this shared-process suite
1730+ -- -- doing so would tear down the library and crash the next sibling test.
1731+
1732+ -- The bindings must exist.
1733+ assert_true (type (tidesdb .init ) == " function" , " tidesdb.init should be a function" )
1734+ assert_true (type (tidesdb .finalize ) == " function" , " tidesdb.finalize should be a function" )
1735+
1736+ -- Exercise the real finalize -> init(custom allocators) -> use cycle in an
1737+ -- isolated child process so it cannot disturb other tests, and so the init
1738+ -- return-value contract can be asserted deterministically (ordering is fixed
1739+ -- here: finalize first, so the next init is the first one and succeeds).
1740+ local script = [[
1741+ local tidesdb = require("tidesdb")
1742+ tidesdb.finalize()
1743+ -- nil keeps the system allocators; this is where a real caller would pass custom
1744+ -- malloc/calloc/realloc/free. The first init after finalize installs them...
1745+ assert(tidesdb.init(nil, nil, nil, nil) == 0, "first init after finalize should return 0")
1746+ -- ...and a second init is rejected because allocators are already locked in.
1747+ assert(tidesdb.init(nil, nil, nil, nil) == -1, "init on an already-initialized library should return -1")
1748+ local path = "./test_db_init_child"
1749+ os.execute("rm -rf " .. path)
1750+ local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
1751+ db:create_column_family("c")
1752+ local cf = db:get_column_family("c")
1753+ local txn = db:begin_txn()
1754+ txn:put(cf, "k", "v")
1755+ txn:commit()
1756+ txn:free()
1757+ db:close()
1758+ os.execute("rm -rf " .. path)
1759+ print("CHILD_OK")
1760+ ]]
1761+ local tmp = " ./_init_finalize_child.lua"
1762+ local fh = assert (io.open (tmp , " w" ))
1763+ fh :write (script )
1764+ fh :close ()
1765+
1766+ -- Reuse the interpreter and environment the suite is already running under.
1767+ local interp = (arg and arg [- 1 ]) or " luajit"
1768+ local p = assert (io.popen (interp .. " " .. tmp .. " 2>&1" ))
1769+ local out = p :read (" *a" )
1770+ p :close ()
1771+ os.execute (" rm -f " .. tmp )
1772+ assert_true (out :find (" CHILD_OK" , 1 , true ) ~= nil ,
1773+ " isolated init/finalize cycle should succeed, got:\n " .. tostring (out ))
17441774 print (" PASS: test_init_finalize" )
17451775end
17461776
0 commit comments