From 6656ebd31ebad9eb4be44d6146253461a846b04f Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 27 Jul 2026 13:18:19 +0200 Subject: [PATCH] Install test logger via ctor instead of per-test calls Unit tests in src/ had no logger at all, so LEECH2_LOG produced no output for them. A #[ctor] constructor in each test binary installs it once. Signed-off-by: Lars Erik Wik --- Cargo.lock | 23 +++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 15 +++++++++++++++ tests/accept_composite_pk.rs | 1 - tests/accept_compression_fallback.rs | 1 - tests/accept_config.rs | 18 ------------------ tests/accept_config_change.rs | 1 - tests/accept_config_include.rs | 1 - tests/accept_data_format.rs | 8 -------- tests/accept_field_reorder.rs | 1 - tests/accept_filters.rs | 12 ------------ tests/accept_genesis.rs | 4 ---- tests/accept_hash_prefix.rs | 1 - tests/accept_injected_fields.rs | 9 --------- tests/accept_missing_csv.rs | 1 - tests/accept_multi_table.rs | 1 - tests/accept_no_change.rs | 1 - tests/accept_no_compression.rs | 1 - tests/accept_patch.rs | 3 --- tests/accept_recovery.rs | 7 ------- tests/accept_state_dir.rs | 3 --- tests/accept_state_payload.rs | 2 -- tests/accept_stats.rs | 5 ----- tests/accept_truncate.rs | 6 ------ tests/common/mod.rs | 11 ++++++++--- tests/round_trip.rs | 2 -- 26 files changed, 47 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1487e76..4ca5361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bb72bb94fdc1bd619f4c18cc91ecf6302aeb333d31b3c6ec0bb841cd920209" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + [[package]] name = "defmt" version = "1.1.1" @@ -563,6 +573,7 @@ dependencies = [ "chrono", "clap", "csv", + "ctor", "env_logger", "glob", "log", @@ -587,6 +598,18 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "link-section" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc98458dfe90986c5e2f6ddcf68360c7e5c4252600153e06aa4ee8176c0f8d1" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee" + [[package]] name = "linux-raw-sys" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 016f157..9b2042a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ zstd = "0.13" [dev-dependencies] cc = "1" +ctor = "1.0.11" rand = "0.9" tempfile = "3" diff --git a/src/lib.rs b/src/lib.rs index 2428411..ec99a25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,21 @@ pub mod update; pub mod utils; pub mod wire; +/// Install a logger for the unit tests in this crate, before any test runs. +/// Reads the `LEECH2_LOG` env var, same as the CLI and the integration tests. +/// Run `cargo test -- --nocapture` to see the output; libtest only replays +/// captured output for failing tests. +/// +/// Runs before the Rust runtime is fully initialized, hence `unsafe`; the body +/// stays limited to installing the logger. +#[cfg(test)] +#[ctor::ctor(unsafe)] +fn init_test_logging() { + let _ = env_logger::Builder::from_env(env_logger::Env::new().filter("LEECH2_LOG")) + .is_test(true) + .try_init(); +} + /// Install or replace the log callback. /// /// The first call installs the global logger; subsequent calls atomically swap diff --git a/tests/accept_composite_pk.rs b/tests/accept_composite_pk.rs index 3da6e28..90dc19b 100644 --- a/tests/accept_composite_pk.rs +++ b/tests/accept_composite_pk.rs @@ -7,7 +7,6 @@ use leech2::sql; #[test] fn test_composite_primary_keys() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_compression_fallback.rs b/tests/accept_compression_fallback.rs index b65c0e2..4dfaeeb 100644 --- a/tests/accept_compression_fallback.rs +++ b/tests/accept_compression_fallback.rs @@ -11,7 +11,6 @@ const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD]; #[test] fn test_small_payload_kept_raw_when_compression_inflates() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_config.rs b/tests/accept_config.rs index 5111057..4d5e0c2 100644 --- a/tests/accept_config.rs +++ b/tests/accept_config.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_missing_config_file() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let result = Config::load(tmp.path()); assert!(result.is_err()); @@ -23,7 +22,6 @@ fn test_missing_config_file() { #[test] fn test_config_no_primary_key() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -50,7 +48,6 @@ source = "users.csv" #[test] fn test_config_duplicate_field_names() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -77,7 +74,6 @@ source = "users.csv" #[test] fn test_config_invalid_toml_syntax() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config(tmp.path(), "config.toml", "this is not valid toml [[["); let result = Config::load(tmp.path()); @@ -93,7 +89,6 @@ fn test_config_invalid_toml_syntax() { #[test] fn test_config_invalid_json_syntax() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config(tmp.path(), "config.json", "{not valid json}"); let result = Config::load(tmp.path()); @@ -109,7 +104,6 @@ fn test_config_invalid_json_syntax() { #[test] fn test_truncate_config_max_blocks_invalid() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -139,7 +133,6 @@ source = "users.csv" #[test] fn test_truncate_config_max_age_invalid() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -169,7 +162,6 @@ source = "users.csv" #[test] fn test_truncate_config_no_truncate_section() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -192,7 +184,6 @@ source = "users.csv" #[test] fn test_json_config_file() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -230,7 +221,6 @@ fn test_json_config_file() { #[test] fn test_empty_tables_map_rejected() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config(tmp.path(), "config.toml", "[tables]\n"); @@ -243,7 +233,6 @@ fn test_empty_tables_map_rejected() { #[test] fn test_injected_field_collides_with_table_column() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -274,7 +263,6 @@ source = "users.csv" #[test] fn test_filter_references_unknown_field() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -304,7 +292,6 @@ exclude = "^x$" #[test] fn test_empty_csv_source_rejected() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -330,7 +317,6 @@ source = "" #[test] fn test_injected_field_empty_name_rejected() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -360,7 +346,6 @@ source = "users.csv" #[test] fn test_injected_field_value_does_not_parse() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -391,7 +376,6 @@ source = "users.csv" #[test] fn test_field_empty_name_rejected() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -417,7 +401,6 @@ source = "users.csv" #[test] fn test_field_unknown_type_rejected() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), @@ -443,7 +426,6 @@ source = "users.csv" #[test] fn test_compression_level_out_of_range() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); common::write_config( tmp.path(), diff --git a/tests/accept_config_change.rs b/tests/accept_config_change.rs index a9b0967..5abdb6f 100644 --- a/tests/accept_config_change.rs +++ b/tests/accept_config_change.rs @@ -9,7 +9,6 @@ use leech2::sql; /// full state for that table while keeping deltas for unchanged tables. #[test] fn test_config_change_produces_mixed_patch() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_config_include.rs b/tests/accept_config_include.rs index 06c54d4..b60fac2 100644 --- a/tests/accept_config_include.rs +++ b/tests/accept_config_include.rs @@ -10,7 +10,6 @@ use leech2::utils::GENESIS_HASH; /// block creation and patch generation end to end, alongside the base table. #[test] fn test_include_fragment_table_round_trip() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_data_format.rs b/tests/accept_data_format.rs index 48cf40b..f940869 100644 --- a/tests/accept_data_format.rs +++ b/tests/accept_data_format.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_csv_with_header_row() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -50,7 +49,6 @@ header = true #[test] fn test_csv_header_reordered_columns() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -102,7 +100,6 @@ header = true #[test] fn test_empty_csv_table() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -149,7 +146,6 @@ source = "users.csv" #[test] fn test_field_type_sql_quoting() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -218,7 +214,6 @@ source = "records.csv" #[test] fn test_null_sentinel() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -271,7 +266,6 @@ null = "^(N/A)?$" #[test] fn test_custom_boolean_sentinels() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -307,7 +301,6 @@ false = "^N$" #[test] fn test_default_boolean_sentinels_reject_legacy_synonyms() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -339,7 +332,6 @@ fn test_null_pattern_matching_primary_key_value_rejected() { // The null pattern is per-table, so a poorly-chosen pattern can match a // primary-key cell's value. Loading that row must be rejected -- NULL // primary keys would produce broken SQL. - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_field_reorder.rs b/tests/accept_field_reorder.rs index 6130193..d4c4c7e 100644 --- a/tests/accept_field_reorder.rs +++ b/tests/accept_field_reorder.rs @@ -11,7 +11,6 @@ use leech2::sql; /// order in the config is cosmetic. #[test] fn test_field_reorder_in_config_produces_no_delta() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_filters.rs b/tests/accept_filters.rs index a51448a..96effa2 100644 --- a/tests/accept_filters.rs +++ b/tests/accept_filters.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_filter_max_field_length() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -48,7 +47,6 @@ max-field-length = 5 #[test] fn test_filter_exclude_anchored_regex() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -95,7 +93,6 @@ exclude = "^inactive$" #[test] fn test_filter_exclude_unanchored_regex() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -143,7 +140,6 @@ exclude = "DEPRECATED" /// exclude has no effect on a sibling table that lacks its own filter. #[test] fn test_filter_only_applies_to_owning_table() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -192,7 +188,6 @@ source = "orders.csv" #[test] fn test_filter_produces_delete_when_record_starts_matching() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -233,7 +228,6 @@ exclude = "^inactive$" #[test] fn test_filter_produces_insert_when_record_stops_matching() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -274,7 +268,6 @@ exclude = "^inactive$" #[test] fn test_filter_include_keeps_only_matching_records() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -321,7 +314,6 @@ include = "^(active|pending)$" #[test] fn test_filter_include_unanchored_regex() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -369,7 +361,6 @@ include = "PRODUCTION" /// check if at least one listed field matches the pattern. #[test] fn test_filter_include_or_across_fields() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -417,7 +408,6 @@ include = "^active$" #[test] fn test_filter_exclude_wins_over_include() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -460,7 +450,6 @@ exclude = "^pending$" #[test] fn test_filter_produces_delete_when_record_stops_matching_include() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -501,7 +490,6 @@ include = "^active$" #[test] fn test_filter_produces_insert_when_record_starts_matching_include() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_genesis.rs b/tests/accept_genesis.rs index 1ac1297..83dfa45 100644 --- a/tests/accept_genesis.rs +++ b/tests/accept_genesis.rs @@ -9,7 +9,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_empty_patch_before_any_blocks() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -44,7 +43,6 @@ source = "users.csv" #[test] fn test_genesis_block_creation() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -76,7 +74,6 @@ source = "users.csv" #[test] fn test_genesis_patch_all_inserts() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -122,7 +119,6 @@ source = "users.csv" #[test] fn test_noop_patch_when_at_head() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_hash_prefix.rs b/tests/accept_hash_prefix.rs index ad87331..96309a1 100644 --- a/tests/accept_hash_prefix.rs +++ b/tests/accept_hash_prefix.rs @@ -7,7 +7,6 @@ use leech2::sql; #[test] fn test_hash_prefix_resolution() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_injected_fields.rs b/tests/accept_injected_fields.rs index 230b24a..d48aa28 100644 --- a/tests/accept_injected_fields.rs +++ b/tests/accept_injected_fields.rs @@ -9,7 +9,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_injected_field_delta_sql() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -69,7 +68,6 @@ source = "users.csv" #[test] fn test_injected_field_state_sql() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -118,7 +116,6 @@ source = "users.csv" #[test] fn test_no_injected_fields_unchanged_sql() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -154,7 +151,6 @@ source = "users.csv" #[test] fn test_injected_field_integer_type() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -195,7 +191,6 @@ source = "users.csv" #[test] fn test_multiple_injected_fields() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -264,7 +259,6 @@ source = "users.csv" #[test] fn test_runtime_inject_without_static_fields() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -309,7 +303,6 @@ source = "users.csv" #[test] fn test_runtime_inject_appends_alongside_static() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -358,7 +351,6 @@ source = "users.csv" #[test] fn test_runtime_inject_overrides_static_value() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -407,7 +399,6 @@ source = "users.csv" #[test] fn test_multiple_injected_fields_state_sql() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_missing_csv.rs b/tests/accept_missing_csv.rs index 6afd06a..6ae3261 100644 --- a/tests/accept_missing_csv.rs +++ b/tests/accept_missing_csv.rs @@ -5,7 +5,6 @@ use leech2::config::Config; #[test] fn test_block_create_with_missing_csv() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_multi_table.rs b/tests/accept_multi_table.rs index 75187b8..54c2604 100644 --- a/tests/accept_multi_table.rs +++ b/tests/accept_multi_table.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_multiple_tables() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_no_change.rs b/tests/accept_no_change.rs index a3b7b89..d1c41e3 100644 --- a/tests/accept_no_change.rs +++ b/tests/accept_no_change.rs @@ -7,7 +7,6 @@ use leech2::sql; #[test] fn test_unchanged_csv_between_blocks() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_no_compression.rs b/tests/accept_no_compression.rs index 38a0674..71df9f8 100644 --- a/tests/accept_no_compression.rs +++ b/tests/accept_no_compression.rs @@ -12,7 +12,6 @@ const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD]; #[test] fn test_compression_disabled() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_patch.rs b/tests/accept_patch.rs index 2e3891b..ca77c08 100644 --- a/tests/accept_patch.rs +++ b/tests/accept_patch.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_two_blocks_insert_delete_update() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -72,7 +71,6 @@ source = "users.csv" #[test] fn test_three_blocks_chain_consolidation() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -182,7 +180,6 @@ source = "users.csv" #[test] fn test_consecutive_updates_same_column_consolidate() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_recovery.rs b/tests/accept_recovery.rs index a0c0bca..4bdba7b 100644 --- a/tests/accept_recovery.rs +++ b/tests/accept_recovery.rs @@ -33,7 +33,6 @@ source = "users.csv" /// of failing or producing unsafe delta INSERTs. #[test] fn test_reported_block_truncated() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -70,7 +69,6 @@ fn test_reported_block_truncated() { /// may already contain rows). #[test] fn test_reported_file_deleted() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -104,7 +102,6 @@ fn test_reported_file_deleted() { /// ignore the stale STATE file and capture all rows as inserts. #[test] fn test_head_file_deleted() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -140,7 +137,6 @@ fn test_head_file_deleted() { /// Patch creation should fall back to STATE (TRUNCATE + INSERT). #[test] fn test_block_chain_broken() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -176,7 +172,6 @@ fn test_block_chain_broken() { /// the next patch should produce a full state (TRUNCATE + INSERT). #[test] fn test_patch_failed_forces_full_state() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -217,7 +212,6 @@ fn test_patch_failed_forces_full_state() { /// for the fallback path). #[test] fn test_state_file_deleted_with_valid_chain() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); let config = setup_users(work_dir); @@ -250,7 +244,6 @@ fn test_state_file_deleted_with_valid_chain() { /// emit a patch that omits the changed table. #[test] fn test_layout_change_with_missing_state_fails() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_state_dir.rs b/tests/accept_state_dir.rs index 5b56207..d8afe8a 100644 --- a/tests/accept_state_dir.rs +++ b/tests/accept_state_dir.rs @@ -19,7 +19,6 @@ source = "users.csv" /// directory, separate from the config and CSV inputs that sit at the root. #[test] fn test_state_dir_defaults_to_subdir() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -44,7 +43,6 @@ fn test_state_dir_defaults_to_subdir() { /// still read relative to the work directory. #[test] fn test_state_dir_absolute_redirect() { - common::init_logging(); let work = tempfile::tempdir().unwrap(); let state = tempfile::tempdir().unwrap(); let work_dir = work.path(); @@ -76,7 +74,6 @@ fn test_state_dir_absolute_redirect() { /// A relative `state-dir` resolves against the work directory. #[test] fn test_state_dir_relative_to_work_dir() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_state_payload.rs b/tests/accept_state_payload.rs index 1c15682..dfc691c 100644 --- a/tests/accept_state_payload.rs +++ b/tests/accept_state_payload.rs @@ -8,7 +8,6 @@ use leech2::utils::GENESIS_HASH; #[test] fn test_state_payload_when_smaller_than_deltas() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -69,7 +68,6 @@ source = "items.csv" /// change (delta is smaller), the patch should contain both deltas and states. #[test] fn test_mixed_payload_deltas_and_states() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_stats.rs b/tests/accept_stats.rs index f041dcf..471e022 100644 --- a/tests/accept_stats.rs +++ b/tests/accept_stats.rs @@ -45,7 +45,6 @@ fn read_stats(config: &Config) -> Vec { #[test] fn test_stats_enabled_writes_cumulative_records() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -79,7 +78,6 @@ fn test_stats_enabled_writes_cumulative_records() { #[test] fn test_stats_disabled_writes_nothing() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -95,7 +93,6 @@ fn test_stats_disabled_writes_nothing() { #[test] fn test_stats_dry_run_writes_nothing() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -122,7 +119,6 @@ fn sum_saved(entries: &[Value], stage: &str) -> i64 { #[test] fn test_summarize_aggregates_runs() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -169,7 +165,6 @@ fn test_summarize_aggregates_runs() { #[test] fn test_summarize_returns_none_without_stats_file() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/accept_truncate.rs b/tests/accept_truncate.rs index 555c3d1..64526e8 100644 --- a/tests/accept_truncate.rs +++ b/tests/accept_truncate.rs @@ -19,7 +19,6 @@ fn create_block(config: &Config) -> String { #[test] fn test_truncate_max_blocks() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -90,7 +89,6 @@ source = "users.csv" /// then also verify that blocks created within the age window are preserved. #[test] fn test_truncate_max_age() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -143,7 +141,6 @@ source = "users.csv" #[test] fn test_orphaned_blocks_removed() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -217,7 +214,6 @@ source = "users.csv" #[test] fn test_truncate_reported() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -272,7 +268,6 @@ source = "users.csv" #[test] fn test_disable_remove_orphans() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); @@ -315,7 +310,6 @@ source = "users.csv" #[test] fn test_disable_truncate_reported() { - common::init_logging(); let tmp = tempfile::tempdir().unwrap(); let work_dir = tmp.path(); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index ad6abc4..6913077 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -8,9 +8,14 @@ use leech2::patch::Patch; use leech2::sql; use leech2::wire; -/// Initialize logging for tests. Reads the `LEECH2_LOG` env var (same as the -/// CLI). Safe to call multiple times — subsequent calls are no-ops. -pub fn init_logging() { +/// Install a logger for this test binary, before any test runs. Reads the +/// `LEECH2_LOG` env var, same as the CLI. Run `cargo test -- --nocapture` to +/// see the output; libtest only replays captured output for failing tests. +/// +/// Runs before the Rust runtime is fully initialized, hence `unsafe`; the body +/// stays limited to installing the logger. +#[ctor::ctor(unsafe)] +fn init_logging() { let _ = env_logger::Builder::from_env(env_logger::Env::new().filter("LEECH2_LOG")) .is_test(true) .try_init(); diff --git a/tests/round_trip.rs b/tests/round_trip.rs index bca60ae..4027f2b 100644 --- a/tests/round_trip.rs +++ b/tests/round_trip.rs @@ -613,8 +613,6 @@ fn run_round_for_agent( #[test] #[ignore = "requires PGHOST; run via `cargo test -- --include-ignored`"] fn round_trip_multi_agent() { - common::init_logging(); - if env::var("PGHOST").is_err() { eprintln!("round_trip: PGHOST not set, skipping"); return;