// file: crates/ksp-job-api/tests/release_completeness.rs // version: 2 //! Completeness canaries for the current Job API foundation. #[test] fn pre_003_crate_root_export_inventory_is_exact() { let crate_root = include_str!("../src/lib.rs"); let mut actual = std::vec::Vec::new(); for line in crate_root.lines() { let trimmed = line.trim(); if trimmed.starts_with("pub use ") { actual.push(trimmed); } } actual.sort_unstable(); let mut expected = std::vec![ "pub use self::cancellation::JobCancellationToken;", "pub use self::error::ERROR_CODE_JOB_ID_INVALID;", "pub use self::error::ERROR_CODE_JOB_KIND_INVALID;", "pub use self::error::ERROR_CODE_JOB_NOTIFICATION_SEQUENCE_EXHAUSTED;", "pub use self::error::ERROR_CODE_JOB_TRANSITION_INVALID;", "pub use self::identity::JobId;", "pub use self::identity::JobKindCode;", "pub use self::identity::MAX_JOB_ID_BYTES;", "pub use self::identity::MAX_JOB_KIND_CODE_BYTES;", "pub use self::lifecycle::JobCompletion;", "pub use self::lifecycle::JobLifecycle;", "pub use self::lifecycle::JobState;", "pub use self::notification::JobNotification;", "pub use self::notification::JobNotificationSequence;", "pub use self::notification::JobSnapshotFuture;", "pub use self::notification::JobSnapshotSource;", "pub use ksp_core_lib::Error;", "pub use ksp_core_lib::ErrorCode;", "pub use ksp_core_lib::ErrorContext;", "pub use ksp_core_lib::Result;", ]; expected.sort_unstable(); assert_eq!(actual, expected); assert!(!crate_root.contains("pub mod ")); return; } #[test] fn pre_003_production_module_inventory_is_exact() -> std::io::Result<()> { let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); let entries = match std::fs::read_dir(source_root) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut names = std::vec::Vec::new(); for entry in entries { let entry = match entry { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let file_type = match entry.file_type() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if !file_type.is_file() { continue; } let name = match entry.file_name().into_string() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => continue, }; if name.ends_with(".rs") { names.push(name); } } names.sort_unstable(); assert_eq!(names, std::vec!["cancellation.rs", "error.rs", "identity.rs", "lib.rs", "lifecycle.rs", "notification.rs"]); return std::result::Result::Ok(()); } #[test] fn pre_003_surface_opens_only_generic_notifications_without_backfill_or_worker_contracts() { let sources = [ include_str!("../src/cancellation.rs"), include_str!("../src/identity.rs"), include_str!("../src/lib.rs"), include_str!("../src/lifecycle.rs"), include_str!("../src/notification.rs"), ]; for source in sources { for forbidden in ["BackfillRequest", "WorkerControl", "WorkerState", "spawn(", "RawTransaction", "provider", "endpoint"] { assert!(!source.contains(forbidden), "future or domain-specific Job contract leaked early: {forbidden}"); } } let notification_source = include_str!("../src/notification.rs"); assert!(notification_source.contains("pub struct JobNotification")); assert!(notification_source.contains("pub trait JobSnapshotSource")); assert!(notification_source.contains("std::future::Future")); assert!(!notification_source.contains("tokio::")); let lifecycle_source = include_str!("../src/lifecycle.rs"); assert!(lifecycle_source.contains("#[derive(Eq, PartialEq)]\npub struct JobLifecycle")); assert!(!lifecycle_source.contains("#[derive(Clone, Eq, PartialEq)]\npub struct JobLifecycle")); return; }