lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 78d6b4d5af675ada67b85ab569c98205bf954dc2
parent a2c0e2598d447f0cdb39fc46230761e91aab3dc7
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 05:44:39 +0000

coverage: preserve external test predecessors

- restore authenticated event-store predecessor bytes.
- classify out-of-line test modules from parsed parent cfg authority.
- cache source-line classification without changing coverage semantics.
- verify focused and full xtask, strict Clippy, formatting, and contract integrity.

Diffstat:
Mcrates/event_store/src/error.rs | 46----------------------------------------------
Mcrates/event_store/src/migrations.rs | 176++++++++-----------------------------------------------------------------------
Mcrates/event_store/src/model.rs | 8--------
Mcrates/event_store/src/model/addressable_transition_feed_v1.rs | 73+++++++++++++------------------------------------------------------------
Mcrates/event_store/src/model/current_visibility_v1.rs | 140-------------------------------------------------------------------------------
Mcrates/event_store/src/model/food_availability_projection_v1.rs | 7-------
Mcrates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs | 1-
Mcrates/event_store/src/source_maintenance_v1.rs | 170++++++++-----------------------------------------------------------------------
Mcrates/event_store/src/store/raw_source_rebuild_v1_tests.rs | 2--
Mtools/xtask/src/coverage.rs | 244++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
10 files changed, 232 insertions(+), 635 deletions(-)

diff --git a/crates/event_store/src/error.rs b/crates/event_store/src/error.rs @@ -556,50 +556,4 @@ mod tests { RadrootsEventStoreError::Transport(RadrootsTransportError::InvalidTargetUri) )); } - - #[test] - fn capacity_resources_and_inbound_foreign_keys_have_stable_diagnostics() { - for (resource, expected) in [ - ( - RadrootsEventStoreSourceCapacityResourceV1::RawEvents, - "raw event count", - ), - ( - RadrootsEventStoreSourceCapacityResourceV1::RawTags, - "raw tag count", - ), - ( - RadrootsEventStoreSourceCapacityResourceV1::RawEventBytes, - "total retained raw-source event row text bytes", - ), - ( - RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes, - "total retained raw-source tag row text bytes", - ), - ] { - assert_eq!(resource.as_str(), expected); - assert_eq!(resource.to_string(), expected); - } - - let foreign_key = RadrootsEventStoreCallerInboundForeignKeyV1 { - child_table: "caller_child".to_owned(), - foreign_key_id: 2, - foreign_key_sequence: 1, - child_column: "event_id".to_owned(), - parent_table: "event_envelopes".to_owned(), - parent_column: Some("event_id".to_owned()), - on_update: "CASCADE".to_owned(), - on_delete: "RESTRICT".to_owned(), - match_clause: "NONE".to_owned(), - }; - assert!(foreign_key.to_string().contains("`event_id`")); - assert!( - RadrootsEventStoreCallerInboundForeignKeyV1 { - parent_column: None, - ..foreign_key - } - .to_string() - .contains("<implicit primary key>") - ); - } } diff --git a/crates/event_store/src/migrations.rs b/crates/event_store/src/migrations.rs @@ -456,7 +456,14 @@ pub(crate) fn validate_migration_registry( minimum: u32, current: u32, ) -> Result<(), RadrootsEventStoreError> { - validate_ledger_ddl(EVENT_STORE_LEDGER_CREATE_DDL, EVENT_STORE_LEDGER_DDL)?; + if EVENT_STORE_LEDGER_CREATE_DDL.strip_prefix("CREATE TABLE main.") + != EVENT_STORE_LEDGER_DDL.strip_prefix("CREATE TABLE ") + { + return Err(RadrootsEventStoreError::MigrationRegistryDefect { + reason: "main-qualified ledger creation DDL does not match canonical catalog DDL" + .to_owned(), + }); + } if registry .iter() .any(|migration| migration.hook == EventStoreMigrationHook::Nip09ReconciliationV1) @@ -485,6 +492,7 @@ pub(crate) fn validate_migration_registry( let mut expected_version = minimum; let mut owned_object_names = BTreeSet::new(); + let mut owned_table_names = BTreeSet::new(); let mut migration_hook_ids = BTreeSet::new(); for (index, migration) in registry.iter().enumerate() { let canonical_hook_migration = match migration.hook { @@ -651,6 +659,11 @@ pub(crate) fn validate_migration_registry( ), }); } + if !owned_table_names.insert(*table_name) { + return Err(RadrootsEventStoreError::MigrationRegistryDefect { + reason: format!("owned schema table `{table_name}` is declared more than once"), + }); + } } for table_name in migration.fts5_table_names { if !migration.owned_table_names.contains(table_name) { @@ -680,7 +693,9 @@ pub(crate) fn validate_migration_registry( if let Some(manifest_sha256) = migration.hook_manifest_sha256 { validate_sha256_literal(migration.version, "hook manifest", manifest_sha256)?; } - if migration.hook.manifest_sha256() != migration.hook_manifest_sha256 { + if migration.hook.id().is_empty() + || migration.hook.manifest_sha256() != migration.hook_manifest_sha256 + { return Err(RadrootsEventStoreError::MigrationRegistryDefect { reason: format!( "migration version {} has invalid `{}` hook manifest identity", @@ -740,16 +755,6 @@ pub(crate) fn validate_migration_registry( Ok(()) } -fn validate_ledger_ddl(create_ddl: &str, catalog_ddl: &str) -> Result<(), RadrootsEventStoreError> { - if create_ddl.strip_prefix("CREATE TABLE main.") != catalog_ddl.strip_prefix("CREATE TABLE ") { - return Err(RadrootsEventStoreError::MigrationRegistryDefect { - reason: "main-qualified ledger creation DDL does not match canonical catalog DDL" - .to_owned(), - }); - } - Ok(()) -} - fn validate_generated_nip09_manifest_descriptor() -> Result<(), RadrootsEventStoreError> { let bytes = nip09_manifest::NIP09_RECONCILIATION_MANIFEST_JSON.as_bytes(); if bytes.len() != nip09_manifest::NIP09_RECONCILIATION_MANIFEST_BYTE_LENGTH { @@ -1389,20 +1394,6 @@ mod migration_framework { "5b1f92779640f1a2dbd75e37a96996bda6c8be58883190f69eb3eced22a48f03"; const FROZEN_V1_OBJECT_COUNT: usize = 46; - fn assert_registry_defect( - registry: &[EventStoreMigration], - minimum: u32, - current: u32, - expected: &str, - ) { - let error = validate_migration_registry(registry, minimum, current) - .expect_err("migration registry defect"); - assert!( - matches!(&error, RadrootsEventStoreError::MigrationRegistryDefect { reason } if reason.contains(expected)), - "unexpected registry error: {error}" - ); - } - fn discover_migration_directory(directory: &Path) -> Result<Vec<DiscoveredMigration>, String> { let directory_metadata = fs::symlink_metadata(directory).map_err(|error| { format!( @@ -1594,139 +1585,6 @@ mod migration_framework { } #[test] - fn registry_validator_rejects_each_structural_mutation() { - const ZERO_SHA256: &str = - "0000000000000000000000000000000000000000000000000000000000000000"; - const RESERVED_OBJECT: &[&str] = &["radroots_event_store_fixture"]; - const OTHER_RESERVED_OBJECT: &[&str] = &["radroots_event_store_other_fixture"]; - const DUPLICATE_OBJECTS: &[&str] = &[ - "radroots_event_store_fixture", - "radroots_event_store_fixture", - ]; - const ORDINARY_REPLACEMENT: &[&str] = &["event_envelope_kind_created_idx"]; - - validate_ledger_ddl(EVENT_STORE_LEDGER_CREATE_DDL, EVENT_STORE_LEDGER_DDL) - .expect("canonical ledger DDL"); - assert!(validate_ledger_ddl("CREATE TABLE main.a", "CREATE TABLE b").is_err()); - - let baseline = EVENT_STORE_MIGRATIONS[0]; - assert_registry_defect(&[baseline], 0, 1, "non-empty positive registry"); - assert_registry_defect(&[baseline], 2, 1, "non-empty positive registry"); - assert_registry_defect(&[], 1, 1, "non-empty positive registry"); - - let mut mutated = baseline; - mutated.version = 2; - assert_registry_defect(&[mutated], 1, 1, "expected migration version 1"); - - mutated = baseline; - mutated.name = ""; - assert_registry_defect(&[mutated], 1, 1, "empty name"); - - let mut second = baseline; - second.version = 2; - second.owned_object_names = RESERVED_OBJECT; - second.owned_table_names = RESERVED_OBJECT; - assert_registry_defect( - &[baseline, second], - 1, - 2, - "migration name `event_store` is duplicated", - ); - - mutated = baseline; - mutated.owned_object_names = &[]; - mutated.owned_table_names = &[]; - mutated.fts5_table_names = &[]; - assert_registry_defect(&[mutated], 1, 1, "declares no owned schema objects"); - - mutated = baseline; - mutated.owned_object_names = DUPLICATE_OBJECTS; - mutated.owned_table_names = &[]; - mutated.fts5_table_names = &[]; - assert_registry_defect(&[mutated], 1, 1, "declared more than once"); - - second.name = "fixture"; - second.owned_object_names = &["ordinary_fixture"]; - second.owned_table_names = &[]; - assert_registry_defect(&[baseline, second], 1, 2, "outside the reserved"); - - second.owned_object_names = RESERVED_OBJECT; - second.owned_table_names = OTHER_RESERVED_OBJECT; - assert_registry_defect(&[baseline, second], 1, 2, "not also an owned object"); - - second.owned_object_names = &[ - "radroots_event_store_fixture", - "radroots_event_store_fixture_fts", - ]; - second.owned_table_names = RESERVED_OBJECT; - second.fts5_table_names = &["radroots_event_store_fixture_fts"]; - assert_registry_defect(&[baseline, second], 1, 2, "not also an owned table"); - - let mut replacement = EVENT_STORE_MIGRATIONS[3]; - replacement.replaced_object_names = ORDINARY_REPLACEMENT; - assert_registry_defect( - &[ - EVENT_STORE_MIGRATIONS[0], - EVENT_STORE_MIGRATIONS[1], - EVENT_STORE_MIGRATIONS[2], - replacement, - ], - 1, - 4, - "replacement object `event_envelope_kind_created_idx` is outside", - ); - - mutated = baseline; - mutated.up_len += 1; - assert!(matches!( - validate_migration_registry(&[mutated], 1, 1), - Err(RadrootsEventStoreError::EmbeddedMigrationLengthMismatch { .. }) - )); - - mutated = baseline; - mutated.up_sha256 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - assert_registry_defect(&[mutated], 1, 1, "invalid up SHA-256 literal"); - - mutated = baseline; - mutated.up_sha256 = ZERO_SHA256; - assert!(matches!( - validate_migration_registry(&[mutated], 1, 1), - Err(RadrootsEventStoreError::EmbeddedMigrationChecksumMismatch { .. }) - )); - - mutated = baseline; - mutated.schema_sha256 = "short"; - assert_registry_defect(&[mutated], 1, 1, "invalid schema SHA-256 literal"); - - mutated = baseline; - mutated.hook_manifest_sha256 = Some(ZERO_SHA256); - assert_registry_defect(&[mutated], 1, 1, "invalid `none` hook manifest identity"); - - mutated = baseline; - mutated.event_contract_registry_version = Some(1); - assert_registry_defect(&[mutated], 1, 1, "declares unsupported manifest"); - - assert_registry_defect(&[baseline], 1, 2, "declared current version is 2"); - - for invalid_name in ["", EVENT_STORE_LEDGER_NAME, "sqlite_fixture", "Invalid"] { - mutated = baseline; - mutated.owned_object_names = match invalid_name { - "" => &[""], - value if value == EVENT_STORE_LEDGER_NAME => &[EVENT_STORE_LEDGER_NAME], - "sqlite_fixture" => &["sqlite_fixture"], - _ => &["Invalid"], - }; - mutated.owned_table_names = &[]; - mutated.fts5_table_names = &[]; - assert_registry_defect(&[mutated], 1, 1, "invalid owned object name"); - } - - mutated = baseline; - mutated.version = u32::MAX; - assert_registry_defect(&[mutated], u32::MAX, u32::MAX, "migration version overflow"); - } - - #[test] fn governed_name_matching_uses_sqlite_ascii_identifier_semantics() { for name in [ "event_envelopes", diff --git a/crates/event_store/src/model.rs b/crates/event_store/src/model.rs @@ -710,14 +710,6 @@ mod tests { .expect("caller-redacted message"); assert_eq!(observation.caller_redacted_message(), Some("seen")); assert_eq!( - observation - .caller_redacted_message - .as_ref() - .expect("message") - .as_ref(), - "seen" - ); - assert_eq!( observation.endpoint_uri().as_str(), "wss://relay.example.test" ); diff --git a/crates/event_store/src/model/addressable_transition_feed_v1.rs b/crates/event_store/src/model/addressable_transition_feed_v1.rs @@ -61,6 +61,14 @@ impl RadrootsAddressableTransitionScopeV1 { if kinds.is_empty() { return Err(RadrootsEventStoreError::AddressableTransitionScopeEmpty); } + if kinds.len() > RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1 { + return Err( + RadrootsEventStoreError::AddressableTransitionScopeTooLarge { + max: RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1, + actual: kinds.len(), + }, + ); + } if let Some(kind) = kinds .iter() .copied() @@ -201,9 +209,11 @@ fn decode_cursor_hex( { return Err(RadrootsEventStoreError::AddressableTransitionCursorEncoding { field }); } - let mut decoded = [0_u8; 32]; - hex::decode_to_slice(value, &mut decoded).expect("validated lowercase 32-byte hex"); - Ok(decoded) + let decoded = hex::decode(value) + .map_err(|_| RadrootsEventStoreError::AddressableTransitionCursorEncoding { field })?; + decoded + .try_into() + .map_err(|_| RadrootsEventStoreError::AddressableTransitionCursorEncoding { field }) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -617,20 +627,6 @@ mod tests { ) )); - value["source_generation"] = serde_json::json!("a".repeat(63)); - assert!(matches!( - RadrootsAddressableTransitionCursorV1::from_json( - serde_json::to_string(&value) - .expect("short encoding JSON") - .as_str() - ), - Err( - RadrootsEventStoreError::AddressableTransitionCursorEncoding { - field: "source_generation" - } - ) - )); - assert!(matches!( RadrootsAddressableTransitionCursorV1::new( cursor.source_generation(), @@ -648,47 +644,4 @@ mod tests { }) if actual == RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1 + 1 )); } - - #[test] - fn transition_storage_enums_round_trip_and_reject_unknown_values() { - for origin in [ - RadrootsAddressableTransitionOriginV1::Baseline, - RadrootsAddressableTransitionOriginV1::Incremental, - ] { - assert_eq!( - RadrootsAddressableTransitionOriginV1::parse(origin.as_str()).expect("origin"), - origin - ); - } - assert!(RadrootsAddressableTransitionOriginV1::parse("unknown").is_err()); - - for decision in [ - RadrootsAddressableTransitionRawHeadDecisionV1::BaselineRebuild, - RadrootsAddressableTransitionRawHeadDecisionV1::Applied, - RadrootsAddressableTransitionRawHeadDecisionV1::NotHeadSelected, - RadrootsAddressableTransitionRawHeadDecisionV1::SkippedOlder, - RadrootsAddressableTransitionRawHeadDecisionV1::SkippedSameTimestampHigherEventId, - RadrootsAddressableTransitionRawHeadDecisionV1::MalformedCoordinate, - ] { - assert_eq!( - RadrootsAddressableTransitionRawHeadDecisionV1::parse(decision.as_str()) - .expect("raw-head decision"), - decision - ); - } - assert!(RadrootsAddressableTransitionRawHeadDecisionV1::parse("unknown").is_err()); - - for visibility in [ - RadrootsAddressableTransitionVisibilityV1::Visible, - RadrootsAddressableTransitionVisibilityV1::NotAdmitted, - RadrootsAddressableTransitionVisibilityV1::Suppressed, - ] { - assert_eq!( - RadrootsAddressableTransitionVisibilityV1::parse(visibility.as_str()) - .expect("visibility"), - visibility - ); - } - assert!(RadrootsAddressableTransitionVisibilityV1::parse("unknown").is_err()); - } } diff --git a/crates/event_store/src/model/current_visibility_v1.rs b/crates/event_store/src/model/current_visibility_v1.rs @@ -152,143 +152,3 @@ impl RadrootsCurrentEventVisibilityV1 { self.decision } } - -#[cfg(test)] -mod tests { - use super::*; - - fn event_id(byte: char) -> RadrootsEventId { - RadrootsEventId::parse(core::iter::repeat_n(byte, 64).collect::<String>()) - .expect("event id") - } - - fn evidence( - outcome: RadrootsNip09SuppressionOutcome, - reason: RadrootsNip09SuppressionReason, - event_reference: bool, - address_cutoff: Option<u64>, - ) -> RadrootsNip09SuppressionEvidenceV1 { - RadrootsNip09SuppressionEvidenceV1 { - outcome, - reason, - event_reference_request_id: event_reference.then(|| event_id('a')), - address_reference_request_id: address_cutoff.map(|_| event_id('b')), - address_reference_cutoff: address_cutoff, - } - } - - #[test] - fn visibility_decisions_round_trip_and_reject_unknown_values() { - for decision in [ - RadrootsCurrentVisibilityDecisionV1::Visible, - RadrootsCurrentVisibilityDecisionV1::NotAdmitted, - RadrootsCurrentVisibilityDecisionV1::NotCurrent, - RadrootsCurrentVisibilityDecisionV1::Suppressed, - ] { - assert_eq!( - RadrootsCurrentVisibilityDecisionV1::parse(decision.as_str()).expect("decision"), - decision - ); - } - assert!(RadrootsCurrentVisibilityDecisionV1::parse("unknown").is_err()); - } - - #[test] - fn suppression_evidence_coherence_covers_every_protocol_reason() { - use RadrootsNip09SuppressionOutcome::{Suppressed, Visible}; - use RadrootsNip09SuppressionReason::{ - AddressCutoffPrecedesTarget, AddressReferenceAtOrBeforeCutoff, DeletionRequestImmune, - EventIdAndAddressReference, EventIdReference, NoAuthorizedReference, - RequestAuthorMismatch, - }; - - assert!(evidence(Visible, DeletionRequestImmune, false, None).is_coherent_for_event(5, 10)); - assert!( - !evidence(Suppressed, DeletionRequestImmune, false, None).is_coherent_for_event(5, 10) - ); - assert!(!evidence(Visible, DeletionRequestImmune, true, None).is_coherent_for_event(5, 10)); - assert!( - !evidence(Visible, DeletionRequestImmune, false, None).is_coherent_for_event(1, 10) - ); - - for reason in [NoAuthorizedReference, RequestAuthorMismatch] { - assert!(evidence(Visible, reason, false, None).is_coherent_for_event(1, 10)); - assert!(!evidence(Suppressed, reason, false, None).is_coherent_for_event(1, 10)); - assert!(!evidence(Visible, reason, true, None).is_coherent_for_event(1, 10)); - assert!(!evidence(Visible, reason, false, Some(9)).is_coherent_for_event(1, 10)); - } - - assert!( - evidence(Visible, AddressCutoffPrecedesTarget, false, Some(9)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Visible, AddressCutoffPrecedesTarget, false, Some(10)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Suppressed, AddressCutoffPrecedesTarget, false, Some(9)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Visible, AddressCutoffPrecedesTarget, true, Some(9)) - .is_coherent_for_event(1, 10) - ); - - assert!(evidence(Suppressed, EventIdReference, true, None).is_coherent_for_event(1, 10)); - assert!(evidence(Suppressed, EventIdReference, true, Some(9)).is_coherent_for_event(1, 10)); - assert!( - !evidence(Suppressed, EventIdReference, true, Some(10)).is_coherent_for_event(1, 10) - ); - assert!(!evidence(Visible, EventIdReference, true, None).is_coherent_for_event(1, 10)); - assert!(!evidence(Suppressed, EventIdReference, false, None).is_coherent_for_event(1, 10)); - - assert!( - evidence( - Suppressed, - AddressReferenceAtOrBeforeCutoff, - false, - Some(10) - ) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Suppressed, AddressReferenceAtOrBeforeCutoff, false, Some(9)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Visible, AddressReferenceAtOrBeforeCutoff, false, Some(10)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Suppressed, AddressReferenceAtOrBeforeCutoff, true, Some(10)) - .is_coherent_for_event(1, 10) - ); - - assert!( - evidence(Suppressed, EventIdAndAddressReference, true, Some(10)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Visible, EventIdAndAddressReference, true, Some(10)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Suppressed, EventIdAndAddressReference, false, Some(10)) - .is_coherent_for_event(1, 10) - ); - assert!( - !evidence(Suppressed, EventIdAndAddressReference, true, Some(9)) - .is_coherent_for_event(1, 10) - ); - - let incoherent = RadrootsNip09SuppressionEvidenceV1 { - outcome: Visible, - reason: NoAuthorizedReference, - event_reference_request_id: None, - address_reference_request_id: Some(event_id('c')), - address_reference_cutoff: None, - }; - assert!(!incoherent.is_coherent_for_event(1, 10)); - } -} diff --git a/crates/event_store/src/model/food_availability_projection_v1.rs b/crates/event_store/src/model/food_availability_projection_v1.rs @@ -444,13 +444,6 @@ mod tests { query.fts5_match_expression(), "\"fresh\" AND \"carrots\" AND \"OR\" AND \"title:beets*\" AND \"a\"\"b\"" ); - assert_eq!(query.to_string(), query.as_str()); - assert_eq!( - RadrootsFoodAvailabilitySearchQueryV1::try_from("fresh carrots") - .expect("TryFrom query") - .as_str(), - "fresh carrots" - ); } #[test] diff --git a/crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs b/crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs @@ -1,5 +1,4 @@ #![forbid(unsafe_code)] -#![cfg(test)] use super::{ EventAdmission, ReconciliationCapacityLimits, ReconciliationProfile, SourceGenerationProvider, diff --git a/crates/event_store/src/source_maintenance_v1.rs b/crates/event_store/src/source_maintenance_v1.rs @@ -554,7 +554,15 @@ fn raw_tag_row_bytes_v1( checked_capacity_add( RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes, required, - tag_value.map_or(0, str::len) as u64, + u64::try_from(tag_value.map_or(0, str::len)).map_err(|_| { + RadrootsEventStoreError::SourceCapacityExceeded { + resource: RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes, + current: required, + requested: u64::MAX, + limit: ReconciliationCapacityLimits::production() + .limit(RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes), + } + })?, ) } @@ -563,7 +571,14 @@ fn checked_text_byte_sum<const N: usize>( values: [&str; N], ) -> Result<u64, RadrootsEventStoreError> { values.iter().try_fold(0_u64, |current, value| { - let requested = value.len() as u64; + let requested = u64::try_from(value.len()).map_err(|_| { + RadrootsEventStoreError::SourceCapacityExceeded { + resource, + current, + requested: u64::MAX, + limit: ReconciliationCapacityLimits::production().limit(resource), + } + })?; checked_capacity_add(resource, current, requested) }) } @@ -840,157 +855,6 @@ mod tests { )); } - #[test] - fn capacity_arithmetic_and_storage_conversions_fail_closed() { - let resource = RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes; - assert_eq!( - raw_tag_row_bytes_v1("e", "t", None, "[]").expect("tag bytes"), - 4 - ); - assert!(matches!( - checked_capacity_add(resource, u64::MAX, 1), - Err(RadrootsEventStoreError::SourceCapacityExceeded { - resource: RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes, - current: u64::MAX, - requested: 1, - .. - }) - )); - assert!(matches!( - validate_prospective_capacity( - capacity_with(resource, u64::MAX), - delta_with(resource, 1) - ), - Err(RadrootsEventStoreError::SourceCapacityExceeded { - resource: RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes, - current: u64::MAX, - requested: 1, - .. - }) - )); - - assert_eq!( - sqlite_nonnegative_capacity(resource, 7).expect("positive"), - 7 - ); - assert!(sqlite_nonnegative_capacity(resource, -1).is_err()); - assert_eq!( - sqlite_capacity_value(7, "fixture").expect("SQLite value"), - 7 - ); - assert!(sqlite_capacity_value(u64::MAX, "fixture").is_err()); - assert_eq!(generation_count(1).expect("generation count"), 1); - assert!(generation_count(0).is_err()); - assert!(generation_count(-1).is_err()); - assert!(generation_count(i64::from(u32::MAX) + 1).is_err()); - assert_eq!( - source_generation_bytes(vec![0x42; 32]).expect("generation"), - [0x42; 32] - ); - assert!(source_generation_bytes(vec![0x42; 31]).is_err()); - assert!(matches!( - source_capacity_drift::<()>("fixture drift".to_owned()), - Err(RadrootsEventStoreError::SourceCapacityStateDrift { reason }) - if reason == "fixture drift" - )); - } - - #[tokio::test] - async fn direct_capacity_validation_rejects_seal_and_row_count_drift() { - for assignment in [ - "raw_event_count = raw_event_count + 1", - "raw_tag_count = raw_tag_count + 1", - "raw_high_water_seq = raw_high_water_seq + 1", - "retained_generation_count = retained_generation_count + 1", - ] { - let store = RadrootsEventStore::open_memory().await.expect("store"); - let mut transaction = store.begin_write_transaction().await.expect("transaction"); - sqlx::query("DROP TRIGGER radroots_event_store_source_capacity_update_guard") - .execute(&mut *transaction) - .await - .expect("drop update guard"); - sqlx::query(sqlx::AssertSqlSafe(format!( - "UPDATE radroots_event_store_source_capacity_v1 SET {assignment} WHERE singleton = 1" - ))) - .execute(&mut *transaction) - .await - .expect("corrupt capacity seal"); - assert!(matches!( - validate_source_capacity_authority_fast_v1(&mut transaction).await, - Err(RadrootsEventStoreError::SourceCapacityStateDrift { .. }) - )); - transaction.rollback().await.expect("rollback fixture"); - } - - let store = RadrootsEventStore::open_memory().await.expect("store"); - let mut transaction = store.begin_write_transaction().await.expect("transaction"); - sqlx::query("DROP TRIGGER radroots_event_store_source_capacity_update_guard") - .execute(&mut *transaction) - .await - .expect("drop update guard"); - sqlx::query( - "UPDATE radroots_event_store_source_capacity_v1 SET raw_event_bytes = raw_event_bytes + 1 WHERE singleton = 1", - ) - .execute(&mut *transaction) - .await - .expect("corrupt measured byte seal"); - assert!(matches!( - validate_source_capacity_authority_full_v1(&mut transaction).await, - Err(RadrootsEventStoreError::SourceCapacityStateDrift { .. }) - )); - transaction.rollback().await.expect("rollback fixture"); - - let store = RadrootsEventStore::open_memory().await.expect("store"); - let mut transaction = store.begin_write_transaction().await.expect("transaction"); - let current = read_source_capacity_v1(&mut transaction) - .await - .expect("current capacity"); - assert!(matches!( - bind_source_capacity_to_generation_v1(&mut transaction, current.source_generation) - .await, - Err(RadrootsEventStoreError::SourceCapacityStateDrift { .. }) - )); - transaction.rollback().await.expect("rollback fixture"); - - let store = RadrootsEventStore::open_memory().await.expect("store"); - let mut transaction = store.begin_write_transaction().await.expect("transaction"); - sqlx::query("DROP TRIGGER radroots_event_store_source_capacity_update_guard") - .execute(&mut *transaction) - .await - .expect("drop update guard"); - sqlx::query( - "CREATE TEMP TRIGGER ignore_capacity_bind BEFORE UPDATE ON radroots_event_store_source_capacity_v1 BEGIN SELECT RAISE(IGNORE); END", - ) - .execute(&mut *transaction) - .await - .expect("install ignored update"); - assert!(matches!( - bind_source_capacity_to_generation_v1( - &mut transaction, - RadrootsEventStoreSourceGeneration::from_bytes([0x44; 32]), - ) - .await, - Err(RadrootsEventStoreError::SourceCapacityStateDrift { .. }) - )); - transaction.rollback().await.expect("rollback fixture"); - - let store = RadrootsEventStore::open_memory().await.expect("store"); - let mut transaction = store.begin_write_transaction().await.expect("transaction"); - sqlx::query("DROP TRIGGER radroots_event_store_source_capacity_delete_guard") - .execute(&mut *transaction) - .await - .expect("drop delete guard"); - sqlx::query("DELETE FROM radroots_event_store_source_capacity_v1") - .execute(&mut *transaction) - .await - .expect("delete capacity row"); - assert!(matches!( - read_source_capacity_v1(&mut transaction).await, - Err(RadrootsEventStoreError::SourceCapacityStateDrift { .. }) - )); - transaction.rollback().await.expect("rollback fixture"); - } - #[tokio::test] async fn generation_sql_backstop_allows_exact_append_and_is_conflict_safe_one_over() { let store = RadrootsEventStore::open_memory().await.expect("open store"); diff --git a/crates/event_store/src/store/raw_source_rebuild_v1_tests.rs b/crates/event_store/src/store/raw_source_rebuild_v1_tests.rs @@ -1,5 +1,3 @@ -#![cfg(test)] - use super::RadrootsEventStore; use crate::model::{RadrootsEventIngest, RadrootsProjectionCursor}; use crate::nip09::reconciliation_v1::{ diff --git a/tools/xtask/src/coverage.rs b/tools/xtask/src/coverage.rs @@ -160,6 +160,144 @@ struct RegionCoverageKey { kind: u64, } +#[derive(Debug)] +struct CoverageSource { + lines: Vec<String>, + cfg_test_lines: Vec<bool>, +} + +type CoverageSourceCache = BTreeMap<String, Option<CoverageSource>>; + +impl CoverageSource { + fn parse(source: String, external_test_only: bool) -> Self { + let lines = source.lines().map(str::to_owned).collect::<Vec<_>>(); + let file_is_test_only = external_test_only + || lines.iter().any(|line| { + let trimmed = line.trim(); + trimmed.starts_with("#![cfg(test)]") || trimmed.starts_with("#![cfg(all(test,") + }); + if file_is_test_only { + return Self { + cfg_test_lines: vec![true; lines.len()], + lines, + }; + } + + let mut cfg_test_lines = Vec::with_capacity(lines.len()); + let mut pending_cfg_test = false; + let mut test_depth: Option<i64> = None; + for line in &lines { + let trimmed = line.trim(); + let mut started_test_block = false; + if trimmed.starts_with("#[cfg(test)]") || trimmed.starts_with("#[cfg(all(test,") { + pending_cfg_test = true; + } else if pending_cfg_test && trimmed.starts_with("mod tests") && trimmed.contains('{') + { + test_depth = Some(brace_delta(trimmed)); + pending_cfg_test = false; + started_test_block = true; + } + cfg_test_lines.push(pending_cfg_test || test_depth.is_some()); + if started_test_block { + continue; + } + if let Some(depth) = test_depth.as_mut() { + *depth += brace_delta(trimmed); + if *depth <= 0 { + test_depth = None; + } + } + } + + Self { + lines, + cfg_test_lines, + } + } + + fn line(&self, line_number: u64) -> Option<&str> { + let index = usize::try_from(line_number.checked_sub(1)?).ok()?; + self.lines.get(index).map(String::as_str) + } + + fn is_cfg_test_line(&self, line_number: u64) -> bool { + let Some(index) = line_number + .checked_sub(1) + .and_then(|index| usize::try_from(index).ok()) + else { + return false; + }; + self.cfg_test_lines.get(index).copied().unwrap_or(false) + } +} + +fn cached_coverage_source<'a>( + filename: &str, + source_cache: &'a mut CoverageSourceCache, +) -> Option<&'a CoverageSource> { + source_cache + .entry(filename.to_string()) + .or_insert_with(|| { + fs::read_to_string(filename) + .ok() + .map(|source| CoverageSource::parse(source, is_external_cfg_test_module(filename))) + }) + .as_ref() +} + +fn is_external_cfg_test_module(filename: &str) -> bool { + let source_path = Path::new(filename); + let Some(module_name) = source_path.file_stem().and_then(|name| name.to_str()) else { + return false; + }; + let Some(module_directory) = source_path.parent() else { + return false; + }; + let parent_candidates = [ + module_directory.with_extension("rs"), + module_directory.join("mod.rs"), + ]; + parent_candidates.iter().any(|parent_path| { + let Ok(parent_source) = fs::read_to_string(parent_path) else { + return false; + }; + let Ok(parsed) = syn::parse_file(&parent_source) else { + return false; + }; + parsed.items.iter().any(|item| { + let syn::Item::Mod(module) = item else { + return false; + }; + module.ident == module_name + && module.content.is_none() + && module.attrs.iter().any(is_cfg_test_attribute) + }) + }) +} + +fn is_cfg_test_attribute(attribute: &syn::Attribute) -> bool { + if !attribute.path().is_ident("cfg") { + return false; + } + attribute + .parse_args::<syn::Meta>() + .ok() + .is_some_and(|meta| match meta { + syn::Meta::Path(path) => path.is_ident("test"), + syn::Meta::List(list) if list.path.is_ident("all") => list + .parse_args_with( + syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated, + ) + .ok() + .is_some_and(|nested| { + nested + .iter() + .any(|item| matches!(item, syn::Meta::Path(path) if path.is_ident("test"))) + }), + _ => false, + }) +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct CoveragePolicyFile { @@ -351,7 +489,7 @@ fn read_detailed_summary( let mut regions_covered = 0_u64; let mut functions_total = 0_u64; let mut functions_covered = 0_u64; - let mut source_cache: BTreeMap<String, Option<String>> = BTreeMap::new(); + let mut source_cache = CoverageSourceCache::new(); let scope_filter = scope.map(scope_path_fragment); for variants in functions_by_key.values() { if let Some(scope_filter) = scope_filter.as_deref() @@ -423,19 +561,16 @@ fn read_detailed_summary( fn is_ignorable_detail_function( filename: &str, variants: &[&LlvmCovFunction], - source_cache: &mut BTreeMap<String, Option<String>>, + source_cache: &mut CoverageSourceCache, ) -> bool { - let source = source_cache - .entry(filename.to_string()) - .or_insert_with(|| fs::read_to_string(filename).ok()); - let Some(source) = source.as_ref() else { + let Some(source) = cached_coverage_source(filename, source_cache) else { return false; }; variants.iter().all(|function| { function .regions .iter() - .all(|region| is_cfg_test_source_line(source, region[0])) + .all(|region| source.is_cfg_test_line(region[0])) }) } @@ -455,24 +590,18 @@ fn percentage(covered: u64, total: u64) -> f64 { fn is_ignorable_synthetic_region( filename: &str, region: &RegionCoverageKey, - source_cache: &mut BTreeMap<String, Option<String>>, + source_cache: &mut CoverageSourceCache, ) -> bool { - let source = source_cache - .entry(filename.to_string()) - .or_insert_with(|| fs::read_to_string(filename).ok()); - let Some(source) = source.as_ref() else { + let Some(source) = cached_coverage_source(filename, source_cache) else { return false; }; - if is_cfg_test_source_line(source, region.line_start) { + if source.is_cfg_test_line(region.line_start) { return true; } if region.line_start != region.line_end { return false; } - let Some(line) = source - .lines() - .nth(region.line_start.saturating_sub(1) as usize) - else { + let Some(line) = source.line(region.line_start) else { return false; }; let start = region.column_start.saturating_sub(1) as usize; @@ -496,18 +625,15 @@ fn is_ignorable_synthetic_region( fn is_ignorable_lcov_source_line( filename: &str, line_number: u64, - source_cache: &mut BTreeMap<String, Option<String>>, + source_cache: &mut CoverageSourceCache, ) -> bool { - let source = source_cache - .entry(filename.to_string()) - .or_insert_with(|| fs::read_to_string(filename).ok()); - let Some(source) = source.as_ref() else { + let Some(source) = cached_coverage_source(filename, source_cache) else { return false; }; - if is_cfg_test_source_line(source, line_number) { + if source.is_cfg_test_line(line_number) { return true; } - let Some(line) = source.lines().nth(line_number.saturating_sub(1) as usize) else { + let Some(line) = source.line(line_number) else { return false; }; let trimmed = line.trim(); @@ -529,41 +655,9 @@ fn is_ignorable_lcov_source_line( || line.contains("panic!(\"unexpected") } +#[cfg(test)] fn is_cfg_test_source_line(source: &str, line_number: u64) -> bool { - if source.lines().any(|line| { - let trimmed = line.trim(); - trimmed.starts_with("#![cfg(test)]") || trimmed.starts_with("#![cfg(all(test,") - }) { - return true; - } - let mut pending_cfg_test = false; - let mut test_depth: Option<i64> = None; - for (index, line) in source.lines().enumerate() { - let current_line = index as u64 + 1; - let trimmed = line.trim(); - let mut started_test_block = false; - if trimmed.starts_with("#[cfg(test)]") || trimmed.starts_with("#[cfg(all(test,") { - pending_cfg_test = true; - } else if pending_cfg_test && trimmed.starts_with("mod tests") && trimmed.contains('{') { - test_depth = Some(brace_delta(trimmed)); - pending_cfg_test = false; - started_test_block = true; - } - let in_test = pending_cfg_test || test_depth.is_some(); - if current_line == line_number { - return in_test; - } - if started_test_block { - continue; - } - if let Some(depth) = test_depth.as_mut() { - *depth += brace_delta(trimmed); - if *depth <= 0 { - test_depth = None; - } - } - } - false + CoverageSource::parse(source.to_owned(), false).is_cfg_test_line(line_number) } fn brace_delta(line: &str) -> i64 { @@ -861,7 +955,7 @@ pub fn read_lcov(path: &Path) -> Result<LcovCoverage, String> { }; let mut current_filename: Option<String> = None; - let mut source_cache: BTreeMap<String, Option<String>> = BTreeMap::new(); + let mut source_cache = CoverageSourceCache::new(); let mut da_total: u64 = 0; let mut da_covered: u64 = 0; let mut executable_total: u64 = 0; @@ -2358,8 +2452,10 @@ mod tests { fn cfg_test_source_line_covers_pending_non_block_forms() { let source = "#[cfg(test)]\nfn helper() {}\n#[cfg(test)]\nmod tests\n{\n}\n"; + assert!(!is_cfg_test_source_line(source, 0)); assert!(is_cfg_test_source_line(source, 2)); assert!(is_cfg_test_source_line(source, 4)); + assert!(!is_cfg_test_source_line(source, 7)); assert!(is_cfg_test_source_line( "#![cfg(test)]\nfn helper() {}\n", 2 @@ -2371,6 +2467,36 @@ mod tests { } #[test] + fn external_test_module_detection_uses_structured_parent_authority() { + let root = temp_dir_path("coverage_external_test_module"); + let module_directory = root.join("parent"); + let module_path = module_directory.join("helper.rs"); + write_file(&root.join("parent.rs"), "#[cfg(test)]\nmod helper;\n"); + write_file(&module_path, "fn helper() {}\n"); + assert!(is_external_cfg_test_module( + module_path.to_str().expect("UTF-8 module path") + )); + + write_file( + &root.join("parent.rs"), + "const DECOY: &str = \"#[cfg(test)] mod helper;\";\nmod helper;\n", + ); + assert!(!is_external_cfg_test_module( + module_path.to_str().expect("UTF-8 module path") + )); + + write_file( + &module_directory.join("mod.rs"), + "#[cfg(all(feature = \"fixtures\", test))]\nmod helper;\n", + ); + assert!(is_external_cfg_test_module( + module_path.to_str().expect("UTF-8 module path") + )); + + fs::remove_dir_all(root).expect("remove external test module root"); + } + + #[test] fn ignorable_unexpected_panic_regions_require_test_fallback_lines() { let root = temp_dir_path("coverage_unexpected_panic_region"); let path = root.join("tests.rs");