From ead400b6f506980e41491f4930053a133f3f4b64 Mon Sep 17 00:00:00 2001 From: Henry Popp Date: Mon, 14 Sep 2026 00:17:50 -0500 Subject: [PATCH] test: update to new test syntax --- test/integration_test.koja | 432 +++++++++---------------------------- test/message_test.koja | 251 ++++++--------------- test/scram_test.koja | 79 ++----- test/statements_test.koja | 65 ++---- test/value_test.koja | 116 +++------- 5 files changed, 235 insertions(+), 708 deletions(-) diff --git a/test/integration_test.koja b/test/integration_test.koja index 0e18f97..139ee7d 100644 --- a/test/integration_test.koja +++ b/test/integration_test.koja @@ -7,55 +7,38 @@ priv const TEST_HOST: String = "127.0.0.1" priv const TEST_PORT: Int = 5434 struct IntegrationTest - @test "connects with trust auth and runs SELECT" - fn test_trust_select ! String + test "connects with trust auth and runs SELECT" conn = try IntegrationTest.connect_trust() (conn, outcome) = conn.query("SELECT 1 AS one, 'two' AS two") _ = conn.close() - result = outcome rescue e -> fail e.message() + result = try IntegrationTest.unwrap(outcome) - if result.tag != "SELECT 1" - fail "expected tag `SELECT 1`, got `#{result.tag}`" - end - - if result.fields.length() != 2 - fail "expected 2 fields, got #{result.fields.length()}" - end - - if IntegrationTest.cell(result, 0, 0) != Value.Int(1) - fail "expected Int(1) at (0,0)" - end - - if IntegrationTest.cell(result, 0, 1) != Value.String("two") - fail "expected String(two) at (0,1)" - end + assert result.tag == "SELECT 1" + assert result.fields.length() == 2 + assert IntegrationTest.cell(result, 0, 0) == Value.Int(1) + assert IntegrationTest.cell(result, 0, 1) == Value.String("two") end - @test "returns NULL columns as None" - fn test_null_column ! String + test "returns NULL columns as None" conn = try IntegrationTest.connect_trust() (conn, outcome) = conn.query("SELECT NULL AS nothing") _ = conn.close() - result = outcome rescue e -> fail e.message() + result = try IntegrationTest.unwrap(outcome) - match IntegrationTest.cell(result, 0, 0) - Value.Null -> () - other -> fail "expected Null, got #{other.format()}" - end + assert IntegrationTest.cell(result, 0, 0) == Value.Null end - @test "decodes bool, int, float, text, and NULL to typed values" - fn test_typed_round_trip ! String + test "decodes bool, int, float, text, and NULL to typed values" conn = try IntegrationTest.connect_trust() (conn, outcome) = conn.query("SELECT true, 42, 1.5::float8, 'hi', NULL") _ = conn.close() - result = outcome rescue e -> fail e.message() + result = try IntegrationTest.unwrap(outcome) expected: List = [ Value.Bool(true), Value.Int(42), Value.Float(1.5), Value.String("hi"), @@ -65,67 +48,36 @@ struct IntegrationTest index = 0 for want in expected - got = IntegrationTest.cell(result, 0, index) - - if got != want - fail "column #{index} decoded to #{got.format()}, want #{want.format()}" - end + assert IntegrationTest.cell(result, 0, index) == want, "column #{index}" index = index + 1 end end - @test "runs DDL and DML with the right command tags" - fn test_ddl_and_dml_tags ! String + test "runs DDL and DML with the right command tags" conn = try IntegrationTest.connect_trust() (conn, created) = conn.query( "CREATE TEMP TABLE driver_test (id INT, label TEXT)", ) - - match IntegrationTest.expect_tag(created, "CREATE TABLE") - Result.Err(e) -> - _ = conn.close() - fail e - - Result.Ok(_) -> - () - end + try IntegrationTest.expect_tag(created, "CREATE TABLE") (conn, inserted) = conn.query( "INSERT INTO driver_test VALUES (7, 'seven'), (8, NULL)", ) - - match IntegrationTest.expect_tag(inserted, "INSERT 0 2") - Result.Err(e) -> - _ = conn.close() - fail e - - Result.Ok(_) -> - () - end + try IntegrationTest.expect_tag(inserted, "INSERT 0 2") (conn, selected) = conn.query("SELECT label FROM driver_test ORDER BY id") _ = conn.close() - result = selected rescue e -> fail e.message() - - if result.rows.length() != 2 - fail "expected 2 rows, got #{result.rows.length()}" - end - - if IntegrationTest.cell(result, 0, 0) != Value.String("seven") - fail "expected String(seven) in row 0" - end + result = try IntegrationTest.unwrap(selected) - match IntegrationTest.cell(result, 1, 0) - Value.Null -> () - other -> fail "expected Null, got #{other.format()}" - end + assert result.rows.length() == 2 + assert IntegrationTest.cell(result, 0, 0) == Value.String("seven") + assert IntegrationTest.cell(result, 1, 0) == Value.Null end - @test "surfaces server errors with severity and SQLSTATE" - fn test_server_error ! String + test "surfaces server errors with severity and SQLSTATE" conn = try IntegrationTest.connect_trust() (conn, outcome) = conn.query("SELEC 1") @@ -136,37 +88,29 @@ struct IntegrationTest fail "expected a syntax error, got a result" Result.Err(Error.Server(server_error)) -> - if server_error.code != "42601" - fail "expected SQLSTATE 42601, got #{server_error.code}" - end - - if server_error.severity != "ERROR" - fail "expected severity ERROR, got #{server_error.severity}" - end + assert server_error.code == "42601" + assert server_error.severity == "ERROR" Result.Err(e) -> - fail "expected Error.Server, got: " <> e.message() + fail "expected Error.Server, got: #{e.message()}" end end - @test "connects with a cleartext password" - fn test_cleartext_auth ! String + test "connects with a cleartext password" config = Config.new(TEST_HOST, TEST_PORT, "koja_password", TEST_DATABASE) .with_password("koja_password_secret") try IntegrationTest.connect_and_select_user(config, "koja_password") end - @test "connects with SCRAM-SHA-256" - fn test_scram_auth ! String + test "connects with SCRAM-SHA-256" config = Config.new(TEST_HOST, TEST_PORT, "koja_scram", TEST_DATABASE) .with_password("koja_scram_secret") try IntegrationTest.connect_and_select_user(config, "koja_scram") end - @test "rejects a wrong SCRAM password" - fn test_scram_wrong_password ! String + test "rejects a wrong SCRAM password" config = Config.new(TEST_HOST, TEST_PORT, "koja_scram", TEST_DATABASE) .with_password("wrong") @@ -176,17 +120,14 @@ struct IntegrationTest fail "expected authentication to fail" Result.Err(Error.Server(server_error)) -> - if server_error.code != "28P01" - fail "expected SQLSTATE 28P01, got #{server_error.code}" - end + assert server_error.code == "28P01" Result.Err(e) -> - fail "expected a server error, got: " <> e.message() + fail "expected a server error, got: #{e.message()}" end end - @test "fails clearly when a password is needed but missing" - fn test_missing_password ! String + test "fails clearly when a password is needed but missing" config = Config.new(TEST_HOST, TEST_PORT, "koja_scram", TEST_DATABASE) match Connection.connect(config) @@ -198,12 +139,11 @@ struct IntegrationTest () Result.Err(e) -> - fail "expected AuthenticationFailed, got: " <> e.message() + fail "expected AuthenticationFailed, got: #{e.message()}" end end - @test "executes a typed parameterized SELECT without casts" - fn test_execute_select ! String + test "executes a typed parameterized SELECT without casts" conn = try IntegrationTest.connect_trust() # Declared parameter OIDs type $1 and $2 as int8, so the SQL @@ -212,48 +152,26 @@ struct IntegrationTest (conn, outcome) = conn.execute("SELECT $1 + $2 AS total", params) _ = conn.close() - result = outcome rescue e -> fail e.message() + result = try IntegrationTest.unwrap(outcome) - if result.tag != "SELECT 1" - fail "expected tag `SELECT 1`, got `#{result.tag}`" - end - - if IntegrationTest.cell(result, 0, 0) != Value.Int(42) - fail "expected Int(42) total" - end + assert result.tag == "SELECT 1" + assert IntegrationTest.cell(result, 0, 0) == Value.Int(42) end - @test "executes DML with NULL parameters and reads them back" - fn test_execute_null_param ! String + test "executes DML with NULL parameters and reads them back" conn = try IntegrationTest.connect_trust() (conn, created) = conn.execute( "CREATE TEMP TABLE exec_test (id INT, label TEXT)", ) - - match IntegrationTest.expect_tag(created, "CREATE TABLE") - Result.Err(e) -> - _ = conn.close() - fail e - - Result.Ok(_) -> - () - end + try IntegrationTest.expect_tag(created, "CREATE TABLE") insert_params = [Value.int(1), Value.null()] (conn, inserted) = conn.execute( "INSERT INTO exec_test VALUES ($1, $2)", insert_params, ) - - match IntegrationTest.expect_tag(inserted, "INSERT 0 1") - Result.Err(e) -> - _ = conn.close() - fail e - - Result.Ok(_) -> - () - end + try IntegrationTest.expect_tag(inserted, "INSERT 0 1") select_params = [Value.int(1)] (conn, selected) = conn.execute( @@ -262,133 +180,70 @@ struct IntegrationTest ) _ = conn.close() - result = selected rescue e -> fail e.message() + result = try IntegrationTest.unwrap(selected) - if result.rows.length() != 1 - fail "expected 1 row, got #{result.rows.length()}" - end - - match IntegrationTest.cell(result, 0, 0) - Value.Null -> () - other -> fail "expected Null label, got #{other.format()}" - end + assert result.rows.length() == 1 + assert IntegrationTest.cell(result, 0, 0) == Value.Null end - @test "surfaces server errors from execute" - fn test_execute_server_error ! String + test "surfaces server errors from execute" conn = try IntegrationTest.connect_trust() (conn, outcome) = conn.execute("SELECT * FROM missing_table") _ = conn.close() match outcome - Result.Ok(_) -> - fail "expected an error for a missing table" - + Result.Ok(_) -> fail "expected an error for a missing table" Result.Err(Error.Server(server_error)) -> - if server_error.code != "42P01" - fail "expected SQLSTATE 42P01, got #{server_error.code}" - end - - Result.Err(e) -> - fail "expected Error.Server, got: " <> e.message() + assert server_error.code == "42P01" + Result.Err(e) -> fail "expected Error.Server, got: #{e.message()}" end end - @test "execute reuses a cached prepared statement" - fn test_statement_cache_reuse ! String + test "execute reuses a cached prepared statement" conn = try IntegrationTest.connect_trust() first_params = [Value.int(1)] (conn, first) = conn.execute("SELECT $1 AS n", first_params) - - match first - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(first) second_params = [Value.int(2)] (conn, second) = conn.execute("SELECT $1 AS n", second_params) + result = try IntegrationTest.unwrap(second) - result = - match second - Result.Ok(r) -> - r - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end - - if IntegrationTest.cell(result, 0, 0) != Value.Int(2) - _ = conn.close() - fail "expected Int(2) from the cached statement" - end + assert IntegrationTest.cell(result, 0, 0) == Value.Int(2) (conn, names) = IntegrationTest.prepared_names(conn) _ = conn.close() - listed = names rescue e -> fail e + listed = try IntegrationTest.unwrap(names) - if listed.rows.length() != 1 - fail "expected 1 prepared statement, got #{listed.rows.length()}" - end - - if IntegrationTest.cell(listed, 0, 0) != Value.String("koja_s1") - fail "expected statement koja_s1" - end + assert listed.rows.length() == 1 + assert IntegrationTest.cell(listed, 0, 0) == Value.String("koja_s1") end - @test "the cache evicts the least recently used statement" - fn test_statement_cache_eviction ! String + test "the cache evicts the least recently used statement" config = Config.new(TEST_HOST, TEST_PORT, "koja_trust", TEST_DATABASE) .with_statement_cache_size(1) conn = try IntegrationTest.connect_config(config) (conn, first) = conn.execute("SELECT 1 AS a") - - match first - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(first) (conn, second) = conn.execute("SELECT 2 AS b") - - match second - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(second) (conn, names) = IntegrationTest.prepared_names(conn) _ = conn.close() - listed = names rescue e -> fail e + listed = try IntegrationTest.unwrap(names) - if listed.rows.length() != 1 - fail "expected the first statement to be closed, " <> - "got #{listed.rows.length()} prepared" - end - - if IntegrationTest.cell(listed, 0, 0) != Value.String("koja_s2") - fail "expected only koja_s2 to remain" - end + assert listed.rows.length() == 1, "expected the first statement to be closed" + assert IntegrationTest.cell(listed, 0, 0) == Value.String("koja_s2") end - @test "a zero cache size prepares nothing" - fn test_statement_cache_disabled ! String + test "a zero cache size prepares nothing" config = Config.new(TEST_HOST, TEST_PORT, "koja_trust", TEST_DATABASE) .with_statement_cache_size(0) @@ -396,34 +251,19 @@ struct IntegrationTest params = [Value.int(7)] (conn, outcome) = conn.execute("SELECT $1 AS n", params) + result = try IntegrationTest.unwrap(outcome) - result = - match outcome - Result.Ok(r) -> - r - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end - - if IntegrationTest.cell(result, 0, 0) != Value.Int(7) - _ = conn.close() - fail "expected Int(7) from the uncached statement" - end + assert IntegrationTest.cell(result, 0, 0) == Value.Int(7) (conn, names) = IntegrationTest.prepared_names(conn) _ = conn.close() - listed = names rescue e -> fail e + listed = try IntegrationTest.unwrap(names) - if listed.rows.length() != 0 - fail "expected no prepared statements, got #{listed.rows.length()}" - end + assert listed.rows.length() == 0 end - @test "a schema change under a cached statement re-prepares" - fn test_stale_statement_schema_change ! String + test "a schema change under a cached statement re-prepares" conn = try IntegrationTest.connect_trust() setup = [ @@ -435,144 +275,87 @@ struct IntegrationTest for sql in setup (next, outcome) = conn.query(sql) conn = next - - match outcome - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(outcome) end (conn, first) = conn.execute("SELECT * FROM stale_plan_probe") - - match first - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(first) # Adding a column changes the result shape of `SELECT *`, which # invalidates the cached plan (SQLSTATE 0A000) on the next run. (conn, altered) = conn.query( "ALTER TABLE stale_plan_probe ADD COLUMN b int", ) - - match altered - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(altered) (conn, second) = conn.execute("SELECT * FROM stale_plan_probe") (conn, _) = conn.query("DROP TABLE stale_plan_probe") _ = conn.close() - result = - match second - Result.Ok(r) -> r - Result.Err(e) -> - fail "expected a re-prepare after the schema change, got: " <> - e.message() - end + result = try IntegrationTest.unwrap(second) - if result.fields.length() != 2 - fail "expected 2 columns after re-prepare, got #{result.fields.length()}" - end + assert result.fields.length() == 2, "expected a re-prepare after the schema change" end - @test "a deallocated statement re-prepares" - fn test_stale_statement_deallocated ! String + test "a deallocated statement re-prepares" conn = try IntegrationTest.connect_trust() first_params = [Value.int(1)] (conn, first) = conn.execute("SELECT $1 AS n", first_params) - - match first - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(first) # DEALLOCATE ALL drops every prepared statement server-side, like # a pooler running DISCARD ALL between checkouts. (conn, dropped) = conn.query("DEALLOCATE ALL") - - match dropped - Result.Ok(_) -> - () - - Result.Err(e) -> - _ = conn.close() - fail e.message() - end + try IntegrationTest.unwrap(dropped) second_params = [Value.int(9)] (conn, second) = conn.execute("SELECT $1 AS n", second_params) (conn, names) = IntegrationTest.prepared_names(conn) _ = conn.close() - result = - match second - Result.Ok(r) -> r - Result.Err(e) -> - fail "expected a re-prepare after DEALLOCATE ALL, got: " <> - e.message() - end + result = try IntegrationTest.unwrap(second) - if IntegrationTest.cell(result, 0, 0) != Value.Int(9) - fail "expected Int(9) from the re-prepared statement" - end + assert IntegrationTest.cell(result, 0, 0) == Value.Int(9), "expected a re-prepare after DEALLOCATE ALL" - listed = names rescue e -> fail e + listed = try IntegrationTest.unwrap(names) - if listed.rows.length() != 1 - fail "expected 1 re-prepared statement, got #{listed.rows.length()}" - end + assert listed.rows.length() == 1 end - priv fn connect_and_select_user(config: Config, want_user: String) ! String - conn = - match Connection.connect(config) - Result.Ok(c) -> c - Result.Err(e) -> - fail "connect failed (is docker compose up?): " <> e.message() - end + priv fn connect_and_select_user(config: Config, want_user: String) + ! Test.Failure + + conn = try IntegrationTest.connect_config(config) (conn, outcome) = conn.query("SELECT current_user") _ = conn.close() - result = outcome rescue e -> fail e.message() + result = try IntegrationTest.unwrap(outcome) - if IntegrationTest.cell(result, 0, 0) != Value.String(want_user) - fail "expected current_user `#{want_user}`" - end + assert IntegrationTest.cell(result, 0, 0) == Value.String(want_user) end - priv fn connect_trust -> Result - IntegrationTest.connect_config( + priv fn connect_trust -> Connection ! Test.Failure + try IntegrationTest.connect_config( Config.new(TEST_HOST, TEST_PORT, "koja_trust", TEST_DATABASE), ) end - priv fn connect_config(config: Config) -> Result + priv fn connect_config(config: Config) -> Connection ! Test.Failure match Connection.connect(config) - Result.Ok(conn) -> - Result.Ok(conn) - + Result.Ok(conn) -> conn Result.Err(e) -> - Result.Err("connect failed (is docker compose up?): " <> e.message()) + fail "connect failed (is docker compose up?): #{e.message()}" + end + end + + # Unwraps a driver outcome, failing the test with the error's message + # instead of its `Debug` rendering. + priv fn unwrap(outcome: Result) -> T ! Test.Failure + match outcome + Result.Ok(value) -> value + Result.Err(e) -> fail Test.Failure.Error(e.message()) end end @@ -593,27 +376,16 @@ struct IntegrationTest # The statement names the server currently holds, for cache # assertions. Uses the simple protocol, which the cache ignores. priv fn prepared_names(conn: Connection) - -> (Connection, Result) + -> (Connection, Result) - (conn, outcome) = conn.query( - "SELECT name FROM pg_prepared_statements ORDER BY name", - ) - - match outcome - Result.Ok(result) -> (conn, Result.Ok(result)) - Result.Err(e) -> (conn, Result.Err(e.message())) - end + conn.query("SELECT name FROM pg_prepared_statements ORDER BY name") end - priv fn expect_tag(outcome: Result, want: String) ! String - match outcome - Result.Ok(result) -> - if result.tag != want - fail "expected tag `#{want}`, got `#{result.tag}`" - end + priv fn expect_tag(outcome: Result, want: String) + ! Test.Failure - Result.Err(e) -> - fail e.message() - end + result = try IntegrationTest.unwrap(outcome) + + assert result.tag == want end end diff --git a/test/message_test.koja b/test/message_test.koja index 38d06ae..f06564f 100644 --- a/test/message_test.koja +++ b/test/message_test.koja @@ -1,17 +1,11 @@ struct MessageTest - @test "encode_startup frames the protocol version and parameters" - fn test_encode_startup ! String + test "encode_startup frames the protocol version and parameters" encoded = Message.encode_startup("alice", "appdb") match encoded <> -> - if len != encoded.byte_size() - fail "length prefix #{len} != #{encoded.byte_size()}" - end - - if version != 196608 - fail "expected protocol 196608, got #{version}" - end + assert len == encoded.byte_size() + assert version == 196608 try MessageTest.check_startup_params(rest) @@ -20,26 +14,22 @@ struct MessageTest end end - priv fn check_startup_params(params: Binary) ! String + priv fn check_startup_params(params: Binary) ! Test.Failure expected = ["user", "alice", "database", "appdb"] try MessageTest.check_cstrings(params, expected, 0) end priv fn check_cstrings(data: Binary, expected: List, index: Int) - ! String + ! Test.Failure match expected.get(index) Option.None -> - if data != <<0>> - fail "startup params missing final terminator" - end + assert data == <<0>>, "startup params missing final terminator" Option.Some(want) -> match Wire.take_cstring(data) Option.Some((actual, rest)) -> - if actual != want - fail "expected `#{want}`, got `#{actual}`" - end + assert actual == want try MessageTest.check_cstrings(rest, expected, index + 1) @@ -49,28 +39,17 @@ struct MessageTest end end - @test "encode_query wraps the SQL in a Q frame" - fn test_encode_query ! String + test "encode_query wraps the SQL in a Q frame" encoded = Message.encode_query("SELECT 1") match encoded <> -> - if tag != 0x51 - fail "expected tag Q (0x51), got #{tag}" - end - - if len != payload.byte_size() + 4 - fail "length prefix #{len} != #{payload.byte_size() + 4}" - end + assert tag == 0x51 + assert len == payload.byte_size() + 4 match Wire.take_cstring(payload) - Option.Some((sql, _)) -> - if sql != "SELECT 1" - fail "expected `SELECT 1`, got `#{sql}`" - end - - Option.None -> - fail "payload is not a NUL-terminated string" + Option.Some((sql, _)) -> assert sql == "SELECT 1" + Option.None -> fail "payload is not a NUL-terminated string" end _ -> @@ -78,15 +57,11 @@ struct MessageTest end end - @test "TERMINATE is a bare X frame" - fn test_terminate ! String - if TERMINATE != <<0x58, 4::32>> - fail "expected <> frame, got #{TERMINATE}" - end + test "TERMINATE is a bare X frame" + assert TERMINATE == <<0x58, 4::32>> end - @test "decode recognizes ReadyForQuery" - fn test_decode_ready_for_query ! String + test "decode recognizes ReadyForQuery" frame = MessageTest.frame(90, <<73::8>>) match Message.decode(frame) @@ -95,23 +70,16 @@ struct MessageTest end end - @test "decode extracts the CommandComplete tag" - fn test_decode_command_complete ! String + test "decode extracts the CommandComplete tag" frame = MessageTest.frame(67, MessageTest.cstring("INSERT 0 1")) match Message.decode(frame) - Backend.CommandComplete(tag) -> - if tag != "INSERT 0 1" - fail "expected `INSERT 0 1`, got `#{tag}`" - end - - _ -> - fail "expected Backend.CommandComplete" + Backend.CommandComplete(tag) -> assert tag == "INSERT 0 1" + _ -> fail "expected Backend.CommandComplete" end end - @test "decode collects severity, code, and message from ErrorResponse" - fn test_decode_error_response ! String + test "decode collects severity, code, and message from ErrorResponse" body = << 83, MessageTest.cstring("ERROR"), 86, MessageTest.cstring("ERROR"), 67, MessageTest.cstring("42601"), 77, @@ -120,25 +88,16 @@ struct MessageTest match Message.decode(MessageTest.frame(69, body)) Backend.ErrorResponse(server_error) -> - if server_error.severity != "ERROR" - fail "expected severity ERROR, got #{server_error.severity}" - end - - if server_error.code != "42601" - fail "expected code 42601, got #{server_error.code}" - end - - if server_error.message != "syntax error at or near \"SELEC\"" - fail "unexpected message: #{server_error.message}" - end + assert server_error.severity == "ERROR" + assert server_error.code == "42601" + assert server_error.message == "syntax error at or near \"SELEC\"" _ -> fail "expected Backend.ErrorResponse" end end - @test "decode reads column names and type OIDs from RowDescription" - fn test_decode_row_description ! String + test "decode reads column names and type OIDs from RowDescription" body = << 2::16, MessageTest.cstring("id"), MessageTest.field_metadata(23), MessageTest.cstring("name"), MessageTest.field_metadata(25) @@ -146,9 +105,7 @@ struct MessageTest match Message.decode(MessageTest.frame(84, body)) Backend.RowDescription(fields) -> - if fields.length() != 2 - fail "expected 2 fields, got #{fields.length()}" - end + assert fields.length() == 2 try MessageTest.expect_field(fields.get(0), "id", 23) @@ -159,25 +116,13 @@ struct MessageTest end end - @test "decode reads text and NULL columns from DataRow" - fn test_decode_data_row ! String + test "decode reads text and NULL columns from DataRow" body = <<2::16, 5::32, "hello", 4294967295::32>> match Message.decode(MessageTest.frame(68, body)) Backend.DataRow(columns) -> - if columns.length() != 2 - fail "expected 2 columns, got #{columns.length()}" - end - - match columns.get(0) - Option.Some(Option.Some(text)) -> - if text != "hello" - fail "expected `hello`, got `#{text}`" - end - - _ -> - fail "expected first column to be Some" - end + assert columns.length() == 2 + assert columns.get(0) == Option.Some(Option.Some("hello")) match columns.get(1) Option.Some(Option.None) -> () @@ -189,114 +134,78 @@ struct MessageTest end end - @test "encode_parse frames the unnamed statement with no types" - fn test_encode_parse ! String + test "encode_parse frames the unnamed statement with no types" no_oids: List = [] encoded = Message.encode_parse("", "SELECT $1", no_oids) match encoded <> -> - if tag != 0x50 - fail "expected tag P (0x50), got #{tag}" - end - - if len != body.byte_size() + 4 - fail "length prefix #{len} != #{body.byte_size() + 4}" - end + assert tag == 0x50 + assert len == body.byte_size() + 4 # Body: empty statement name, the SQL text, then zero # pre-declared parameter types. - (name, rest) = - match Wire.take_cstring(body) - Option.Some(p) -> p - Option.None -> fail "missing statement name" - end - - if name != "" - fail "expected unnamed statement, got `#{name}`" - end + (name, rest) = try Test.require( + Wire.take_cstring(body).or_err("missing statement name"), + ) - (sql, rest) = - match Wire.take_cstring(rest) - Option.Some(p) -> p - Option.None -> fail "missing SQL text" - end + assert name == "" - if sql != "SELECT $1" - fail "expected `SELECT $1`, got `#{sql}`" - end + (sql, rest) = try Test.require( + Wire.take_cstring(rest).or_err("missing SQL text"), + ) - if rest != <<0::16>> - fail "expected zero parameter types, got #{rest}" - end + assert sql == "SELECT $1" + assert rest == <<0::16>>, "expected zero parameter types" _ -> fail "parse message shorter than its header" end end - @test "encode_parse declares a name and parameter type OIDs" - fn test_encode_parse_named ! String + test "encode_parse declares a name and parameter type OIDs" oids: List = [20, 16] encoded = Message.encode_parse("s1", "SELECT $1, $2", oids) match encoded <<0x50, _len::32, body: Binary>> -> - (name, rest) = - match Wire.take_cstring(body) - Option.Some(p) -> p - Option.None -> fail "missing statement name" - end - - if name != "s1" - fail "expected statement `s1`, got `#{name}`" - end + (name, rest) = try Test.require( + Wire.take_cstring(body).or_err("missing statement name"), + ) - (_, rest) = - match Wire.take_cstring(rest) - Option.Some(p) -> p - Option.None -> fail "missing SQL text" - end + assert name == "s1" - if rest != <<2::16, 20::32, 16::32>> - fail "expected int8 and bool OIDs, got #{rest}" - end + (_, rest) = try Test.require( + Wire.take_cstring(rest).or_err("missing SQL text"), + ) + + assert rest == <<2::16, 20::32, 16::32>>, "expected int8 and bool OIDs" _ -> fail "parse message shorter than its header" end end - @test "encode_bind carries text and NULL parameters" - fn test_encode_bind ! String + test "encode_bind carries text and NULL parameters" params: List> = [Option.Some("42"), Option.None] encoded = Message.encode_bind("", params) match encoded <> -> - if tag != 0x42 - fail "expected tag B (0x42), got #{tag}" - end - - if len != body.byte_size() + 4 - fail "length prefix #{len} != #{body.byte_size() + 4}" - end + assert tag == 0x42 + assert len == body.byte_size() + 4 # Body: portal "", statement "", 0 format codes, 2 params: # int32 2 + "42", int32 -1 (NULL), then 0 result formats. - expected = <<0, 0, 0::16, 2::16, 2::32, 52, 50, 4294967295::32, 0::16>> - - if body != expected - fail "bind body #{body} != expected #{expected}" - end + assert body == + <<0, 0, 0::16, 2::16, 2::32, 52, 50, 4294967295::32, 0::16>> _ -> fail "bind message shorter than its header" end end - @test "encode_bind names the statement it binds" - fn test_encode_bind_named ! String + test "encode_bind names the statement it binds" no_params: List> = [] encoded = Message.encode_bind("s7", no_params) @@ -304,44 +213,24 @@ struct MessageTest <<0x42, _len::32, body: Binary>> -> # Body: portal "", statement "s7", 0 format codes, 0 params, # 0 result formats. - expected = <<0, 115, 55, 0, 0::16, 0::16, 0::16>> - - if body != expected - fail "bind body #{body} != expected #{expected}" - end + assert body == <<0, 115, 55, 0, 0::16, 0::16, 0::16>> _ -> fail "bind message shorter than its header" end end - @test "encode_close frames a statement Close" - fn test_encode_close ! String - encoded = Message.encode_close("s3") - expected = <<0x43, 8::32, 0x53, 115, 51, 0>> - - if encoded != expected - fail "close frame #{encoded} != expected #{expected}" - end + test "encode_close frames a statement Close" + assert Message.encode_close("s3") == <<0x43, 8::32, 0x53, 115, 51, 0>> end - @test "DESCRIBE_PORTAL, EXECUTE, and SYNC frame correctly" - fn test_describe_execute_sync ! String - if DESCRIBE_PORTAL != <<0x44, 6::32, 0x50, 0>> - fail "unexpected Describe frame: #{DESCRIBE_PORTAL}" - end - - if EXECUTE != <<0x45, 9::32, 0, 0::32>> - fail "unexpected Execute frame: #{EXECUTE}" - end - - if SYNC != <<0x53, 4::32>> - fail "unexpected Sync frame: #{SYNC}" - end + test "DESCRIBE_PORTAL, EXECUTE, and SYNC frame correctly" + assert DESCRIBE_PORTAL == <<0x44, 6::32, 0x50, 0>> + assert EXECUTE == <<0x45, 9::32, 0, 0::32>> + assert SYNC == <<0x53, 4::32>> end - @test "decode recognizes the extended-protocol responses" - fn test_decode_extended_responses ! String + test "decode recognizes the extended-protocol responses" match Message.decode(MessageTest.frame(49, <<>>)) Backend.ParseComplete -> () _ -> fail "expected Backend.ParseComplete for '1'" @@ -373,8 +262,7 @@ struct MessageTest end end - @test "decode maps unknown tags to Other" - fn test_decode_unknown ! String + test "decode maps unknown tags to Other" match Message.decode(MessageTest.frame(33, <<>>)) Backend.Other -> () _ -> fail "expected Backend.Other" @@ -397,17 +285,12 @@ struct MessageTest end priv fn expect_field(actual: Option, want: String, want_oid: Int) - ! String + ! Test.Failure match actual Option.Some(field) -> - if field.name != want - fail "expected `#{want}`, got `#{field.name}`" - end - - if field.type_oid != want_oid - fail "expected OID #{want_oid} for `#{want}`, got #{field.type_oid}" - end + assert field.name == want + assert field.type_oid == want_oid, "OID for `#{want}`" Option.None -> fail "expected `#{want}`, got None" diff --git a/test/scram_test.koja b/test/scram_test.koja index 4f8d860..4d2ee68 100644 --- a/test/scram_test.koja +++ b/test/scram_test.koja @@ -4,85 +4,52 @@ priv const RFC_CLIENT_NONCE: String = "rOprNGfwEbeRWgbNEkqO" priv const RFC_SERVER_FIRST: String = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096" struct ScramTest - @test "computes the RFC 7677 client proof" - fn test_rfc_client_final ! String - proof = - match Scram.proof("pencil", "user", RFC_CLIENT_NONCE, RFC_SERVER_FIRST) - Result.Ok(p) -> p - Result.Err(e) -> fail e - end + test "computes the RFC 7677 client proof" + proof = try Test.require( + Scram.proof("pencil", "user", RFC_CLIENT_NONCE, RFC_SERVER_FIRST), + ) expected = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=" - if proof.client_final != expected - fail "client-final mismatch:\n got #{proof.client_final}\n want #{expected}" - end + assert proof.client_final == expected end - @test "computes the RFC 7677 server signature" - fn test_rfc_server_signature ! String - proof = - match Scram.proof("pencil", "user", RFC_CLIENT_NONCE, RFC_SERVER_FIRST) - Result.Ok(p) -> p - Result.Err(e) -> fail e - end + test "computes the RFC 7677 server signature" + proof = try Test.require( + Scram.proof("pencil", "user", RFC_CLIENT_NONCE, RFC_SERVER_FIRST), + ) expected = "6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=" - if proof.server_signature != expected - fail "server signature mismatch: got #{proof.server_signature}" - end - - match Scram.verify_server_final("v=" <> expected, proof.server_signature) - Result.Ok(_) -> () - Result.Err(e) -> fail e - end + assert proof.server_signature == expected + assert Scram.verify_server_final("v=" <> expected, proof.server_signature) + .ok?() end - @test "rejects a wrong server signature" - fn test_verify_rejects_bad_signature ! String - match Scram.verify_server_final("v=AAAA", "BBBB") - Result.Ok(_) -> fail "expected verification to fail" - Result.Err(_) -> () - end + test "rejects a wrong server signature" + assert Scram.verify_server_final("v=AAAA", "BBBB").err?() end - @test "rejects a server nonce that drops the client nonce" - fn test_rejects_foreign_nonce ! String + test "rejects a server nonce that drops the client nonce" tampered = "r=stolen-nonce,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096" - match Scram.proof("pencil", "user", RFC_CLIENT_NONCE, tampered) - Result.Ok(_) -> fail "expected foreign nonce to be rejected" - Result.Err(_) -> () - end + assert Scram.proof("pencil", "user", RFC_CLIENT_NONCE, tampered).err?() end - @test "rejects malformed server-first messages" - fn test_rejects_malformed_server_first ! String - match Scram.proof("pencil", "user", RFC_CLIENT_NONCE, "garbage") - Result.Ok(_) -> fail "expected garbage to be rejected" - Result.Err(_) -> () - end + test "rejects malformed server-first messages" + assert Scram.proof("pencil", "user", RFC_CLIENT_NONCE, "garbage").err?() missing_iterations = "r=rOprNGfwEbeRWgbNEkqOabc,s=W22ZaJ0SNY7soEsUEjb6gQ==" - match Scram.proof("pencil", "user", RFC_CLIENT_NONCE, missing_iterations) - Result.Ok(_) -> fail "expected missing iterations to be rejected" - Result.Err(_) -> () - end + assert Scram.proof("pencil", "user", RFC_CLIENT_NONCE, missing_iterations) + .err?() end - @test "generated nonces are printable and distinct" - fn test_generate_nonce ! String + test "generated nonces are printable and distinct" a = Scram.generate_nonce() b = Scram.generate_nonce() - if a.length() != 24 - fail "expected 24-char nonce, got #{a.length()}" - end - - if a == b - fail "two nonces were identical" - end + assert a.length() == 24 + assert a != b end end diff --git a/test/statements_test.koja b/test/statements_test.koja index b21c8c7..50d0616 100644 --- a/test/statements_test.koja +++ b/test/statements_test.koja @@ -1,57 +1,38 @@ struct StatementsTest - @test "a zero limit disables the cache" - fn test_disabled ! String - not StatementCache.new(0).enabled?() + test "a zero limit disables the cache" + assert not StatementCache.new(0).enabled?() end - @test "the key covers SQL text and parameter OIDs" - fn test_key ! String - with_params = StatementCache.key("SELECT $1", [23, 25]) - - if with_params != "SELECT $1|23|25" - fail "got `#{with_params}`" - end + test "the key covers SQL text and parameter OIDs" + assert StatementCache.key("SELECT $1", [23, 25]) == "SELECT $1|23|25" no_params: List = List.new() - if StatementCache.key("SELECT 1", no_params) != "SELECT 1" - fail "expected the bare SQL as the key" - end + assert StatementCache.key("SELECT 1", no_params) == "SELECT 1" end - @test "hit misses on an empty cache and finds a stored name" - fn test_hit_and_store ! String + test "hit misses on an empty cache and finds a stored name" cache = StatementCache.new(4) (cache, missed) = cache.hit("k") - match missed - Option.Some(name) -> fail "expected a miss, got `#{name}`" - Option.None -> () - end + assert missed.none?() cache = cache.store("k", "koja_s1") (_, found) = cache.hit("k") - match found - Option.Some("koja_s1") -> () - Option.Some(name) -> fail "expected koja_s1, got `#{name}`" - Option.None -> fail "expected a hit after store" - end + assert found == Option.Some("koja_s1") end - @test "reserve claims sequential statement names" - fn test_reserve_names ! String + test "reserve claims sequential statement names" cache = StatementCache.new(4) (cache, first, _) = cache.reserve() (_, second, _) = cache.reserve() - if not (first == "koja_s1" and second == "koja_s2") - fail "got `#{first}` and `#{second}`" - end + assert first == "koja_s1" + assert second == "koja_s2" end - @test "a full cache evicts the least recently used entry" - fn test_lru_eviction ! String + test "a full cache evicts the least recently used entry" cache = StatementCache.new(2) cache = cache.store("a", "koja_s1") cache = cache.store("b", "koja_s2") @@ -60,34 +41,22 @@ struct StatementsTest (cache, _) = cache.hit("a") (cache, _, close_frame) = cache.reserve() - if close_frame != Message.encode_close("koja_s2") - fail "expected a Close frame for the evicted koja_s2" - end + assert close_frame == Message.encode_close("koja_s2") (cache, kept) = cache.hit("a") - match kept - Option.Some(_) -> () - Option.None -> fail "expected `a` to survive eviction" - end + assert kept.some?(), "expected `a` to survive eviction" (_, evicted) = cache.hit("b") - match evicted - Option.None -> () - Option.Some(_) -> fail "expected `b` to be evicted" - end + assert evicted.none?(), "expected `b` to be evicted" end - @test "remove drops an entry" - fn test_remove ! String + test "remove drops an entry" cache = StatementCache.new(4) cache = cache.store("k", "koja_s1") (_, found) = cache.remove("k").hit("k") - match found - Option.None -> () - Option.Some(_) -> fail "expected the entry to be gone" - end + assert found.none?() end end diff --git a/test/value_test.koja b/test/value_test.koja index dd00323..acdc33d 100644 --- a/test/value_test.koja +++ b/test/value_test.koja @@ -1,6 +1,5 @@ struct ValueTest - @test "opt_* constructors wrap Some and map None to Null" - fn test_opt_constructors ! String + test "opt_* constructors wrap Some and map None to Null" cases: List<(Value, Value)> = [ (Value.opt_bool(Option.Some(true)), Value.Bool(true)), (Value.opt_int(Option.Some(7)), Value.Int(7)), @@ -15,71 +14,30 @@ struct ValueTest for pair in cases (got, want) = pair - if got != want - fail "expected #{want.format()}, got #{got.format()}" - end + assert got == want end end - @test "as_* accessors read the matching variant and reject others" - fn test_as_accessors ! String - if Value.Int(7).as_int() != Option.Some(7) - fail "as_int on Int" - end - - if Value.Bool(true).as_bool() != Option.Some(true) - fail "as_bool on Bool" - end - - if Value.Float(1.5).as_float() != Option.Some(1.5) - fail "as_float on Float" - end - - if Value.String("x").as_string() != Option.Some("x") - fail "as_string on String" - end - - no_int: Option = Option.None - no_string: Option = Option.None - - if Value.String("7").as_int() != no_int - fail "as_int on String should miss" - end + test "as_* accessors read the matching variant and reject others" + assert Value.Int(7).as_int() == Option.Some(7) + assert Value.Bool(true).as_bool() == Option.Some(true) + assert Value.Float(1.5).as_float() == Option.Some(1.5) + assert Value.String("x").as_string() == Option.Some("x") - if Value.Null.as_string() != no_string - fail "as_string on Null should miss" - end + assert Value.String("7").as_int().none?() + assert Value.Null.as_string().none?() end - @test "*_or accessors fall back on other variants" - fn test_or_accessors ! String - if Value.Int(7).int_or(0) != 7 - fail "int_or on Int" - end - - if Value.Null.int_or(-1) != -1 - fail "int_or fallback" - end - - if Value.String("x").string_or("") != "x" - fail "string_or on String" - end - - if Value.Int(7).string_or("miss") != "miss" - fail "string_or fallback" - end - - if not Value.Bool(true).bool_or(false) - fail "bool_or on Bool" - end - - if Value.Float(1.5).float_or(0.0) != 1.5 - fail "float_or on Float" - end + test "*_or accessors fall back on other variants" + assert Value.Int(7).int_or(0) == 7 + assert Value.Null.int_or(-1) == -1 + assert Value.String("x").string_or("") == "x" + assert Value.Int(7).string_or("miss") == "miss" + assert Value.Bool(true).bool_or(false) + assert Value.Float(1.5).float_or(0.0) == 1.5 end - @test "encode renders each variant in text wire format" - fn test_encode ! String + test "encode renders each variant in text wire format" cases: List<(Value, Option)> = [ (Value.Null, Option.None), (Value.Bool(true), Option.Some("t")), (Value.Bool(false), Option.Some("f")), (Value.Int(42), Option.Some("42")), @@ -90,14 +48,11 @@ struct ValueTest for pair in cases (value, want) = pair - if value.encode() != want - fail "unexpected encoding for #{value.format()}" - end + assert value.encode() == want, "encoding #{value.format()}" end end - @test "type_oid declares int8, bool, and float8, and 0 otherwise" - fn test_type_oid ! String + test "type_oid declares int8, bool, and float8, and 0 otherwise" cases: List<(Value, Int)> = [ (Value.Null, 0), (Value.Bool(true), 16), (Value.Int(1), 20), (Value.Float(1.5), 701), (Value.String("x"), 0) @@ -106,14 +61,11 @@ struct ValueTest for pair in cases (value, want) = pair - if value.type_oid() != want - fail "unexpected OID for #{value.format()}" - end + assert value.type_oid() == want, "OID of #{value.format()}" end end - @test "decode maps known OIDs to typed variants" - fn test_decode_typed ! String + test "decode maps known OIDs to typed variants" cases: List<(Int, String, Value)> = [ (16, "t", Value.Bool(true)), (16, "f", Value.Bool(false)), (20, "9000000000", Value.Int(9000000000)), (21, "7", Value.Int(7)), @@ -123,26 +75,18 @@ struct ValueTest for triple in cases (oid, text, want) = triple - got = Value.decode(oid, Option.Some(text)) - if got != want - fail "OID #{oid} `#{text}` decoded to #{got.format()}" - end + assert Value.decode(oid, Option.Some(text)) == want, "OID #{oid} `#{text}`" end end - @test "decode returns Null for NULL columns" - fn test_decode_null ! String + test "decode returns Null for NULL columns" no_column: Option = Option.None - match Value.decode(23, no_column) - Value.Null -> () - other -> fail "expected Null, got #{other.format()}" - end + assert Value.decode(23, no_column) == Value.Null end - @test "decode passes unparseable and unknown values through as String" - fn test_decode_passthrough ! String + test "decode passes unparseable and unknown values through as String" cases: List<(Int, String)> = [ # numeric keeps arbitrary precision as text (1700, "12345.6789"), @@ -155,15 +99,7 @@ struct ValueTest for pair in cases (oid, text) = pair - match Value.decode(oid, Option.Some(text)) - Value.String(got) -> - if got != text - fail "expected `#{text}`, got `#{got}`" - end - - other -> - fail "OID #{oid} decoded to #{other.format()}" - end + assert Value.decode(oid, Option.Some(text)) == Value.String(text), "OID #{oid}" end end end