diff --git a/Cargo.lock b/Cargo.lock index df730a5..87a1c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,18 +168,18 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 69b18ce..0e66947 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" rust-version = "1.62.1" [dependencies] +serde = { version = "1.0.103", features = ["derive"] } serde_json = "1.0.25" libloading = "0.8" libc = "0.2.64" @@ -13,8 +14,5 @@ rustc_version_runtime = "0.3" once_cell = "1" linkme = "0.3.17" -[dev-dependencies] -serde = { version = "1.0.60", features = ["derive"] } - [features] no-antithesis-sdk = [] diff --git a/lib/src/assert/macros.rs b/lib/src/assert/macros.rs index 7978a3e..77a3d50 100644 --- a/lib/src/assert/macros.rs +++ b/lib/src/assert/macros.rs @@ -1,12 +1,27 @@ +/// Common handling used by all the assertion-related macros #[cfg(not(feature="no-antithesis-sdk"))] #[doc(hidden)] #[macro_export] macro_rules! assert_helper { - (condition = $condition:expr, $message:literal, $details:expr, $assert_type:literal, $display_type:literal, must_hit = $must_hit:literal) => {{ + // The handling of this pattern-arm of assert_helper + // is wrapped in a block {} to avoid name collisions + (condition = $condition:expr, $message:literal, $details:expr, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{ // Force evaluation of expressions. let condition = $condition; let details = $details; + // Define a do-nothing function 'f()' within the context of + // the function invoking an assertion. Then the type_name of + // this do-nothing will be something like: + // + // bincrate::binmod::do_stuff::f + // + // After trimming off the last three chars `::f` what remains is + // the full path to the name of the function invoking the assertion + // + // Both the untrimmed `NAME` and trimmed `FUN_NAME` are lazily + // initialized statics so that `FUN_NAME` can be available at + // assertion catalog registration time. use $crate::once_cell::sync::Lazy; fn f(){} fn type_name_of(_: T) -> &'static str { @@ -31,30 +46,34 @@ macro_rules! assert_helper { id: $message }; - let function = Lazy::force(&FUN_NAME); + let ptr_function = Lazy::force(&FUN_NAME); + $crate::assert::assert_impl( $assert_type, /* assert_type */ - $display_type, /* display_type */ + $display_type.to_owned(), /* display_type */ condition, /* condition */ - $message, /* message */ - ::std::module_path!(), /* class */ - function, /* function */ - ::std::file!(), /* file */ + $message.to_owned(), /* message */ + ::std::module_path!().to_owned(), /* class */ + String::from(*ptr_function), /* function */ + ::std::file!().to_owned(), /* file */ ::std::line!(), /* line */ ::std::column!(), /* column */ true,/* hit */ $must_hit, /* must-hit */ - $message, /* id */ + $message.to_owned(), /* id */ details /* details */ ) - }} + }} // end pattern-arm block } #[cfg(feature="no-antithesis-sdk")] #[doc(hidden)] #[macro_export] macro_rules! assert_helper { - (condition = $condition:expr, $message:literal, $details:expr, $assert_type:literal, $display_type:literal, must_hit = $must_hit:literal) => {{ - // Force evaluation of expressions. + (condition = $condition:expr, $message:literal, $details:expr, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{ + // Force evaluation of expressions, ensuring that + // any side effects of these expressions will always be + // evaluated at runtime - even if the assertion itself + // is supressed by the `no-antithesis-sdk` feature let condition = $condition; let details = $details; }} @@ -64,9 +83,9 @@ macro_rules! assert_helper { /// called at least once. This test property will be viewable in the "Antithesis SDK: Always" /// group of your triage report. #[macro_export] -macro_rules! always { +macro_rules! assert_always { ($condition:expr, $message:literal, $details:expr) => { - $crate::assert_helper!(condition = $condition, $message, $details, "always", "Always", must_hit = true) + $crate::assert_helper!(condition = $condition, $message, $details, AssertType::Always, "Always", must_hit = true) } } @@ -75,9 +94,9 @@ macro_rules! always { /// failing if the function is never invoked. This test property will be viewable in /// the "Antithesis SDK: Always" group of your triage report. #[macro_export] -macro_rules! always_or_unreachable { +macro_rules! assert_always_or_unreachable { ($condition:expr, $message:literal, $details:expr) => { - $crate::assert_helper!(condition = $condition, $message, $details, "always", "AlwaysOrUnreachable", must_hit = false) + $crate::assert_helper!(condition = $condition, $message, $details, AssertType::Always, "AlwaysOrUnreachable", must_hit = false) } } @@ -86,9 +105,9 @@ macro_rules! always_or_unreachable { /// is never called, or if condition is false every time that it is called. This /// test property will be viewable in the "Antithesis SDK: Sometimes" group. #[macro_export] -macro_rules! sometimes { +macro_rules! assert_sometimes { ($condition:expr, $message:literal, $details:expr) => { - $crate::assert_helper!(condition = $condition, $message, $details, "sometimes", "Sometimes", must_hit = true) + $crate::assert_helper!(condition = $condition, $message, $details, AssertType::Sometimes, "Sometimes", must_hit = true) } } @@ -96,9 +115,9 @@ macro_rules! sometimes { /// Reachable will be marked as failing if this function is never called. This test /// property will be viewable in the "Antithesis SDK: Reachablity assertions" group. #[macro_export] -macro_rules! reachable { +macro_rules! assert_reachable { ($message:literal, $details:expr) => { - $crate::assert_helper!(condition = true, $message, $details, "reachability", "Reachable", must_hit = true) + $crate::assert_helper!(condition = true, $message, $details, AssertType::Reachability, "Reachable", must_hit = true) } } @@ -106,9 +125,9 @@ macro_rules! reachable { /// will be marked as failing if this function is ever called. This test property will /// be viewable in the "Antithesis SDK: Reachablity assertions" group. #[macro_export] -macro_rules! unreachable { +macro_rules! assert_unreachable { ($message:literal, $details:expr) => { - $crate::assert_helper!(condition = false, $message, $details, "reachability", "Unreachable", must_hit = false) + $crate::assert_helper!(condition = false, $message, $details, AssertType::Reachability, "Unreachable", must_hit = false) } } diff --git a/lib/src/assert/mod.rs b/lib/src/assert/mod.rs index b7ca0fc..5965501 100644 --- a/lib/src/assert/mod.rs +++ b/lib/src/assert/mod.rs @@ -1,14 +1,11 @@ use once_cell::sync::Lazy; use serde_json::{Value, json}; +use serde::Serialize; use std::collections::HashMap; use std::sync::{Mutex}; use crate::internal; use linkme::distributed_slice; -// Needed for AssertType -use std::fmt; -use std::str::FromStr; - mod macros; @@ -28,21 +25,20 @@ pub(crate) static ASSERT_TRACKER: Lazy>> = pub(crate) static INIT_CATALOG: Lazy<()> = Lazy::new(|| { let no_details: Value = json!({}); for info in ANTITHESIS_CATALOG.iter() { - let f_name = info.function.as_ref(); - println!("CatAlog Item ==> fn: '{}' display_type: '{}' - '{}' {}[{}]", f_name, info.display_type, info.message, info.file, info.begin_line); + let f_name: &str = info.function.as_ref(); assert_impl( info.assert_type, - info.display_type, + info.display_type.to_owned(), info.condition, - info.message, - info.class, - f_name, - info.file, + info.message.to_owned(), + info.class.to_owned(), + f_name.to_owned(), + info.file.to_owned(), info.begin_line, info.begin_column, false, /* hit */ info.must_hit, - info.id, + info.id.to_owned(), &no_details ); } @@ -69,42 +65,27 @@ impl TrackingInfo { } -#[derive(PartialEq, Debug)] -enum AssertType { +#[derive(Copy, Clone, PartialEq, Debug, Serialize)] +#[serde(rename_all(serialize = "lowercase"))] +pub enum AssertType { Always, Sometimes, Reachability, } -const DEFAULT_ASSERT_TYPE: AssertType = AssertType::Reachability; - -impl FromStr for AssertType { - type Err = (); - fn from_str(input: &str) -> Result { - match input { - "always" => Ok(AssertType::Always), - "sometimes" => Ok(AssertType::Sometimes), - "reachability" => Ok(AssertType::Reachability), - _ => Err(()), - } - } -} - -impl fmt::Display for AssertType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let text = match self { - AssertType::Always => "always", - AssertType::Sometimes => "sometimes", - AssertType::Reachability => "reachability" - }; - write!(f, "{text}") - } +#[derive(Serialize, Debug)] +struct AntithesisLocationInfo { + class: String, + function: String, + file: String, + begin_line: u32, + begin_column: u32, } /// Internal representation for assertion catalog #[derive(Debug)] pub struct CatalogInfo { - pub assert_type: &'static str, + pub assert_type: AssertType, pub display_type: &'static str, pub condition: bool, pub message: &'static str, @@ -117,17 +98,13 @@ pub struct CatalogInfo { pub id: &'static str, } -#[derive(Debug)] +#[derive(Serialize, Debug)] struct AssertionInfo { - assert_type: String, + assert_type: AssertType, display_type: String, condition: bool, message: String, - class: String, - function: String, - file: String, - begin_line: u32, - begin_column: u32, + location: AntithesisLocationInfo, hit: bool, must_hit: bool, id: String, @@ -137,47 +114,44 @@ struct AssertionInfo { impl AssertionInfo { #[allow(clippy::too_many_arguments)] pub fn new( - assert_type: &str, - display_type: &str, + assert_type: AssertType, + display_type: String, condition: bool, - message: &str, - class: &str, - function: &str, - file: & str, + message: String, + class: String, + function: String, + file: String, begin_line: u32, begin_column: u32, hit: bool, must_hit: bool, - id: &str, + id: String, details: &Value) -> Self { - let derived_assert_type = match AssertType::from_str(assert_type) { - Ok(converted_assert_type) => converted_assert_type, - Err(_) => DEFAULT_ASSERT_TYPE + let location = AntithesisLocationInfo { + class, + function, + file, + begin_line, + begin_column, }; - let assert_type_text = derived_assert_type.to_string(); - AssertionInfo{ - assert_type: assert_type_text, - display_type: display_type.to_owned(), + assert_type, + display_type, condition, - message: message.to_owned(), - class: class.to_owned(), - function: function.to_owned(), - file: file.to_owned(), - begin_line, - begin_column, + message, + location, hit, must_hit, - id: id.to_owned(), + id, details: details.clone(), } } - // emit(tracker_entry, assertion) and determines if the assertion - // should actually be emitted: + // AssertionInfo::track_entry() determines if the assertion should + // actually be emitted: // // [X] If this is an assertion catalog // registration (assertion.hit == false) then it is emitted. @@ -220,27 +194,7 @@ impl AssertionInfo { } fn emit(&self) { - let location_info = json!({ - "class": self.class.as_str(), - "function": self.function.as_str(), - "file": self.file.as_str(), - "begin_line": self.begin_line, - "begin_column": self.begin_column, - }); - let assertion_value = json!({ - "antithesis_assert": json!({ - "hit": self.hit, - "must_hit": self.must_hit, - "assert_type": self.assert_type.as_str(), - "display_type": self.display_type.as_str(), - "message": self.message.as_str(), - "condition": self.condition, - "id": self.id.as_str(), - "location": location_info, - "details": &self.details - }) - }); - internal::dispatch_output(&assertion_value) + internal::dispatch_output(&self); } } @@ -248,18 +202,18 @@ impl AssertionInfo { #[allow(clippy::too_many_arguments)] pub fn assert_raw( condition: bool, - message: &str, + message: String, details: &Value, - class: &str, - function: &str, - file: &str, + class: String, + function: String, + file: String, begin_line: u32, begin_column: u32, hit: bool, must_hit: bool, - assert_type: &str, - display_type: &str, - id: &str) { + assert_type: AssertType, + display_type: String, + id: String) { assert_impl( assert_type, display_type, condition, message, class, function, file, begin_line, begin_column, hit, must_hit, id, details) } @@ -268,18 +222,18 @@ pub fn assert_raw( /// Regular users of the assert package should not call it. #[allow(clippy::too_many_arguments)] pub fn assert_impl( - assert_type: &str, - display_type: &str, + assert_type: AssertType, + display_type: String, condition: bool, - message: &str, - class: &str, - function: &str, - file: &str, + message: String, + class: String, + function: String, + file: String, begin_line: u32, begin_column: u32, hit: bool, must_hit: bool, - id: &str, + id: String, details: &Value) { let assertion = AssertionInfo::new(assert_type, display_type, condition, message, class, function, file, begin_line, begin_column, hit, must_hit, id, details); @@ -309,44 +263,13 @@ mod tests { } - //-------------------------------------------------------------------------------- - // Tests for AssertType - //-------------------------------------------------------------------------------- - #[test] - fn text_to_assert_type() { - let always_assert_type = AssertType::from_str("always"); - assert_eq!(always_assert_type.unwrap(), AssertType::Always); - - let sometimes_assert_type = AssertType::from_str("sometimes"); - assert_eq!(sometimes_assert_type.unwrap(), AssertType::Sometimes); - - let reachability_assert_type = AssertType::from_str("reachability"); - assert_eq!(reachability_assert_type.unwrap(), AssertType::Reachability); - - let fallback_assert_type = AssertType::from_str("xyz"); - assert!(fallback_assert_type.is_err()) - } - - #[test] - fn assert_type_to_text() { - let always_text = AssertType::Always.to_string(); - assert_eq!(always_text, "always"); - - let sometimes_text = AssertType::Sometimes.to_string(); - assert_eq!(sometimes_text, "sometimes"); - - let reachability_text = AssertType::Reachability.to_string(); - assert_eq!(reachability_text, "reachability"); - } - - //-------------------------------------------------------------------------------- // Tests for AssertionInfo //-------------------------------------------------------------------------------- #[test] fn new_assertion_info_always() { - let this_assert_type = "always"; + let this_assert_type = AssertType::Always; let this_display_type = "Always"; let this_condition = true; let this_message = "Always message"; @@ -365,36 +288,35 @@ mod tests { let ai = AssertionInfo::new( this_assert_type, - this_display_type, + this_display_type.to_owned(), this_condition, - this_message, - this_class, - this_function, - this_file, + this_message.to_owned(), + this_class.to_owned(), + this_function.to_owned(), + this_file.to_owned(), this_begin_line, this_begin_column, this_hit, this_must_hit, - this_id, + this_id.to_owned(), &this_details); - assert_eq!(ai.assert_type, this_assert_type); - assert_eq!(ai.display_type, this_display_type); + assert_eq!(ai.display_type.as_str(), this_display_type); assert_eq!(ai.condition, this_condition); - assert_eq!(ai.message, this_message); - assert_eq!(ai.class, this_class); - assert_eq!(ai.function, this_function); - assert_eq!(ai.file, this_file); - assert_eq!(ai.begin_line, this_begin_line); - assert_eq!(ai.begin_column, this_begin_column); + assert_eq!(ai.message.as_str(), this_message); + assert_eq!(ai.location.class.as_str(), this_class); + assert_eq!(ai.location.function.as_str(), this_function); + assert_eq!(ai.location.file.as_str(), this_file); + assert_eq!(ai.location.begin_line, this_begin_line); + assert_eq!(ai.location.begin_column, this_begin_column); assert_eq!(ai.hit, this_hit); assert_eq!(ai.must_hit, this_must_hit); - assert_eq!(ai.id, this_id); + assert_eq!(ai.id.as_str(), this_id); assert_eq!(ai.details, this_details); } #[test] fn new_assertion_info_sometimes() { - let this_assert_type = "sometimes"; + let this_assert_type = AssertType::Sometimes; let this_display_type = "Sometimes"; let this_condition = true; let this_message = "Sometimes message"; @@ -413,36 +335,35 @@ mod tests { let ai = AssertionInfo::new( this_assert_type, - this_display_type, + this_display_type.to_owned(), this_condition, - this_message, - this_class, - this_function, - this_file, + this_message.to_owned(), + this_class.to_owned(), + this_function.to_owned(), + this_file.to_owned(), this_begin_line, this_begin_column, this_hit, this_must_hit, - this_id, + this_id.to_owned(), &this_details); - assert_eq!(ai.assert_type, this_assert_type); - assert_eq!(ai.display_type, this_display_type); + assert_eq!(ai.display_type.as_str(), this_display_type); assert_eq!(ai.condition, this_condition); - assert_eq!(ai.message, this_message); - assert_eq!(ai.class, this_class); - assert_eq!(ai.function, this_function); - assert_eq!(ai.file, this_file); - assert_eq!(ai.begin_line, this_begin_line); - assert_eq!(ai.begin_column, this_begin_column); + assert_eq!(ai.message.as_str(), this_message); + assert_eq!(ai.location.class.as_str(), this_class); + assert_eq!(ai.location.function.as_str(), this_function); + assert_eq!(ai.location.file.as_str(), this_file); + assert_eq!(ai.location.begin_line, this_begin_line); + assert_eq!(ai.location.begin_column, this_begin_column); assert_eq!(ai.hit, this_hit); assert_eq!(ai.must_hit, this_must_hit); - assert_eq!(ai.id, this_id); + assert_eq!(ai.id.as_str(), this_id); assert_eq!(ai.details, this_details); } #[test] fn new_assertion_info_reachable() { - let this_assert_type = "reachability"; + let this_assert_type = AssertType::Reachability; let this_display_type = "Reachable"; let this_condition = true; let this_message = "Reachable message"; @@ -461,36 +382,35 @@ mod tests { let ai = AssertionInfo::new( this_assert_type, - this_display_type, + this_display_type.to_owned(), this_condition, - this_message, - this_class, - this_function, - this_file, + this_message.to_owned(), + this_class.to_owned(), + this_function.to_owned(), + this_file.to_owned(), this_begin_line, this_begin_column, this_hit, this_must_hit, - this_id, + this_id.to_owned(), &this_details); - assert_eq!(ai.assert_type, this_assert_type); - assert_eq!(ai.display_type, this_display_type); + assert_eq!(ai.display_type.as_str(), this_display_type); assert_eq!(ai.condition, this_condition); - assert_eq!(ai.message, this_message); - assert_eq!(ai.class, this_class); - assert_eq!(ai.function, this_function); - assert_eq!(ai.file, this_file); - assert_eq!(ai.begin_line, this_begin_line); - assert_eq!(ai.begin_column, this_begin_column); + assert_eq!(ai.message.as_str(), this_message); + assert_eq!(ai.location.class.as_str(), this_class); + assert_eq!(ai.location.function.as_str(), this_function); + assert_eq!(ai.location.file.as_str(), this_file); + assert_eq!(ai.location.begin_line, this_begin_line); + assert_eq!(ai.location.begin_column, this_begin_column); assert_eq!(ai.hit, this_hit); assert_eq!(ai.must_hit, this_must_hit); - assert_eq!(ai.id, this_id); + assert_eq!(ai.id.as_str(), this_id); assert_eq!(ai.details, this_details); } #[test] fn assert_impl_pass() { - let this_assert_type = "always"; + let this_assert_type = AssertType::Always; let this_display_type = "Always"; let this_condition = true; let this_message = "Always message 2"; @@ -511,17 +431,17 @@ mod tests { assert_impl( this_assert_type, - this_display_type, + this_display_type.to_owned(), this_condition, - this_message, - this_class, - this_function, - this_file, + this_message.to_owned(), + this_class.to_owned(), + this_function.to_owned(), + this_file.to_owned(), this_begin_line, this_begin_column, this_hit, this_must_hit, - this_id, + this_id.to_owned(), &this_details); let after_tracker = tracking_info_for_key(this_id); @@ -538,7 +458,7 @@ mod tests { #[test] fn assert_impl_fail() { - let this_assert_type = "always"; + let this_assert_type = AssertType::Always; let this_display_type = "Always"; let this_condition = false; let this_message = "Always message 3"; @@ -559,17 +479,17 @@ mod tests { assert_impl( this_assert_type, - this_display_type, + this_display_type.to_owned(), this_condition, - this_message, - this_class, - this_function, - this_file, + this_message.to_owned(), + this_class.to_owned(), + this_function.to_owned(), + this_file.to_owned(), this_begin_line, this_begin_column, this_hit, this_must_hit, - this_id, + this_id.to_owned(), &this_details); let after_tracker = tracking_info_for_key(this_id); @@ -583,57 +503,6 @@ mod tests { }; } - #[test] - fn new_assertion_info_invalid_assert_type() { - let this_assert_type = "possibly"; - let this_display_type = "Possibly"; - let this_condition = true; - let this_message = "Possibly message"; - let this_class = "binary::possibly"; - let this_function = "binary::possibly::possibly_function"; - let this_file = "/home/user/binary/src/possibly_binary.rs"; - let this_begin_line = 13; - let this_begin_column = 8; - let this_hit = true; - let this_must_hit = true; - let this_id = "ID Possibly message"; - let this_details = json!({ - "color": "possibly red", - "extent": 21, - }); - - let ai = AssertionInfo::new( - this_assert_type, - this_display_type, - this_condition, - this_message, - this_class, - this_function, - this_file, - this_begin_line, - this_begin_column, - this_hit, - this_must_hit, - this_id, - &this_details); - - let fallback_assert_type = "reachability"; - - assert_eq!(ai.assert_type, fallback_assert_type); - assert_eq!(ai.display_type, this_display_type); - assert_eq!(ai.condition, this_condition); - assert_eq!(ai.message, this_message); - assert_eq!(ai.class, this_class); - assert_eq!(ai.function, this_function); - assert_eq!(ai.file, this_file); - assert_eq!(ai.begin_line, this_begin_line); - assert_eq!(ai.begin_column, this_begin_column); - assert_eq!(ai.hit, this_hit); - assert_eq!(ai.must_hit, this_must_hit); - assert_eq!(ai.id, this_id); - assert_eq!(ai.details, this_details); - } - fn tracking_info_for_key(key: &str) -> TrackingInfo { // Establish TrackingInfo for this trackingKey when needed diff --git a/lib/src/internal/local_handler.rs b/lib/src/internal/local_handler.rs index 7ff0cce..b5f2a20 100644 --- a/lib/src/internal/local_handler.rs +++ b/lib/src/internal/local_handler.rs @@ -1,57 +1,41 @@ -use serde_json::{Value}; use std::env; use std::fs::File; use std::io::{Write, Error}; +// use crate::internal::noop_handler::NoOpHandler; use crate::internal::{LibHandler}; const LOCAL_OUTPUT: &str = "ANTITHESIS_SDK_LOCAL_OUTPUT"; pub struct LocalHandler { - maybe_writer: Option + writer: File } impl LocalHandler { - pub fn new() -> Self { - let filename = match env::var(LOCAL_OUTPUT) { - Err(_) => return LocalHandler{ maybe_writer: None }, - Ok(s) => s - }; + pub fn new() -> Option { + let filename = env::var(LOCAL_OUTPUT).ok()?; let create_result = File::create(&filename); - if let Ok(f) = create_result { - // Disabling buffering by setting capacity to 0 for now - // Inefficient, but ensures that no buffered bytes are abandoned - // for a LocalHandler instance that does not get Drop'ed - // Seems like LocalHandler gets bound to a reference with - // a 'static lifetime. - LocalHandler{ - maybe_writer: Some(f) - } + if let Ok(writer) = create_result { + Some(LocalHandler{ writer }) } else { - eprintln!("Unable to write to '{}' - {}", filename.as_str(), create_result.unwrap_err()); - LocalHandler { - maybe_writer: None - } + eprintln!("Unable to write to '{}' - {}", filename.as_str(), create_result.unwrap_err()); + None } } } impl LibHandler for LocalHandler { - fn output(&self, value: &Value) -> Result<(), Error> { - match &self.maybe_writer { - Some(writer_ref) => { - let mut writer_mut = writer_ref; - // The compact Display impl (selected using `{}`) of `serde_json::Value` contains no newlines, - // hence we are outputing valid JSONL format here. - // Using the `{:#}` format specifier may results in extra newlines and indentation. - // See https://docs.rs/serde_json/latest/serde_json/enum.Value.html#impl-Display-for-Value. - writeln!(writer_mut, "{}", value)?; - writer_mut.flush()?; - Ok(()) - }, - None => Ok(()) - } + + fn output(&self, value: &str) -> Result<(), Error> { + // The compact Display impl (selected using `{}`) of `serde_json::Value` contains no newlines, + // hence we are outputing valid JSONL format here. + // Using the `{:#}` format specifier may results in extra newlines and indentation. + // See https://docs.rs/serde_json/latest/serde_json/enum.Value.html#impl-Display-for-Value. + let mut writer_mut = & self.writer; + writeln!(writer_mut, "{}", value)?; + writer_mut.flush()?; + Ok(()) } fn random(&self) -> u64 { diff --git a/lib/src/internal/mod.rs b/lib/src/internal/mod.rs index b23843f..2477d88 100644 --- a/lib/src/internal/mod.rs +++ b/lib/src/internal/mod.rs @@ -1,35 +1,61 @@ use once_cell::sync::Lazy; use rustc_version_runtime::version; -use serde_json::{Value, json}; +use serde::Serialize; use std::io::{Error}; use local_handler::LocalHandler; -use voidstar_handler::{VoidstarHandler}; +use voidstar_handler::VoidstarHandler; +use noop_handler::NoOpHandler; mod local_handler; +mod noop_handler; mod voidstar_handler; +#[derive(Serialize, Debug)] +struct AntithesisLanguageInfo { + name: &'static str, + version: String, +} + +#[derive(Serialize, Debug)] +struct AntithesisVersionInfo { + language: AntithesisLanguageInfo, + sdk_version: &'static str, + protocol_version: &'static str, +} + +#[derive(Serialize, Debug)] +struct AntithesisSDKInfo { + antithesis_sdk: AntithesisVersionInfo, +} + // Hardly ever changes, refers to the underlying JSON representation const PROTOCOL_VERSION: &str = "1.0.0"; // Tracks SDK releases -const SDK_VERSION: &str = "0.1.1"; +const SDK_VERSION: &str = "0.1.2"; pub(crate) static LIB_HANDLER: Lazy> = Lazy::new(|| { let handler: Box = match VoidstarHandler::try_load() { Ok(handler) => Box::new(handler), - Err(_) => Box::new(LocalHandler::new()), + Err(_) => match LocalHandler::new() { + Some(h) => Box::new(h), + None => Box::new(NoOpHandler::new()) + } + }; - let _ = handler.output(&sdk_info()); + let s = serde_json::to_string(&sdk_info()).unwrap_or("{}".to_owned()); + let _ = handler.output(s.as_str()); handler }); + pub(crate) trait LibHandler { - fn output(&self, value: &Value) -> Result<(), Error>; + fn output(&self, value: &str) -> Result<(), Error>; fn random(&self) -> u64; } // Made public so it can be invoked from the antithesis_sdk_rust::random module -pub fn dispatch_random() -> u64 { +pub (crate) fn dispatch_random() -> u64 { LIB_HANDLER.random() } @@ -49,23 +75,24 @@ pub fn dispatch_random() -> u64 { // // Made public so it can be invoked from the antithesis_sdk_rust::lifecycle // and antithesis_sdk_rust::assert module -pub fn dispatch_output(json_data: &Value) { - let _ = LIB_HANDLER.output(json_data); +pub fn dispatch_output(json_data: &T) { + let s = serde_json::to_string(json_data).unwrap_or("{}".to_owned()); + let _ = LIB_HANDLER.output(s.as_str()); } -fn sdk_info() -> Value { - let language_info: Value = json!({ - "name": "Rust", - "version": version().to_string() - }); - - let version_info: Value = json!({ - "language": language_info, - "sdk_version": SDK_VERSION, - "protocol_version": PROTOCOL_VERSION - }); - - json!({ - "antithesis_sdk": version_info - }) +fn sdk_info() -> AntithesisSDKInfo { + let language_data = AntithesisLanguageInfo{ + name: "Rust", + version: version().to_string(), + }; + + let version_data = AntithesisVersionInfo{ + language: language_data, + sdk_version: SDK_VERSION, + protocol_version: PROTOCOL_VERSION, + }; + + AntithesisSDKInfo{ + antithesis_sdk: version_data, + } } diff --git a/lib/src/internal/noop_handler.rs b/lib/src/internal/noop_handler.rs new file mode 100644 index 0000000..2276f29 --- /dev/null +++ b/lib/src/internal/noop_handler.rs @@ -0,0 +1,20 @@ +use crate::internal::{LibHandler}; +use std::io::Error; + +pub struct NoOpHandler {} + +impl NoOpHandler { + pub fn new() -> Self { + NoOpHandler{} + } +} + +impl LibHandler for NoOpHandler { + fn output(&self, _value: &str) -> Result<(), Error> { + Ok(()) + } + + fn random(&self) -> u64 { + rand::random::() + } +} diff --git a/lib/src/internal/voidstar_handler.rs b/lib/src/internal/voidstar_handler.rs index 301a1f2..ffbd501 100644 --- a/lib/src/internal/voidstar_handler.rs +++ b/lib/src/internal/voidstar_handler.rs @@ -1,6 +1,5 @@ use libc::{c_char, size_t}; use libloading::{Library}; -use serde_json::{Value}; use std::io::{Error}; use crate::internal::{LibHandler}; @@ -39,12 +38,11 @@ impl VoidstarHandler { } impl LibHandler for VoidstarHandler { - fn output(&self, value: &Value) -> Result<(), Error> { - let payload = value.to_string(); + fn output(&self, value: &str) -> Result<(), Error> { // SAFETY: The data pointer and length passed into `fuzz_json_data` points to valid memory // that we just initialized above. unsafe { - (self.fuzz_json_data)(payload.as_bytes().as_ptr() as *const c_char, payload.len()); + (self.fuzz_json_data)(value.as_bytes().as_ptr() as *const c_char, value.len()); (self.fuzz_flush)(); } Ok(()) diff --git a/lib/src/lifecycle.rs b/lib/src/lifecycle.rs index 12e973b..0a349ba 100644 --- a/lib/src/lifecycle.rs +++ b/lib/src/lifecycle.rs @@ -2,8 +2,20 @@ /// environment that particular test phases or milestones have been reached. use serde_json::{json, Value}; +use serde::Serialize; use crate::internal; +#[derive(Serialize, Debug)] +struct AntithesisSetupData<'a, 'b> { + status: &'a str, + details: &'b Value, +} + +#[derive(Serialize, Debug)] +struct SetupCompleteData<'a> { + antithesis_setup: AntithesisSetupData<'a, 'a>, +} + /// Call this function when your system and workload are fully initialized. /// After this function is called, the Antithesis environment will take a /// snapshot of your system and begin [injecting faults]. @@ -12,13 +24,17 @@ use crate::internal; /// have no effect. Antithesis will treat the first time any process called /// this function as the moment that the setup was completed. pub fn setup_complete(details: &Value) { - let setup_value = json!({ - "antithesis_setup": json!({ - "status": "complete", - "details": details - }) - }); - internal::dispatch_output(&setup_value) + let status = "complete"; + let antithesis_setup = AntithesisSetupData::<'_, '_>{ + status, + details, + }; + + let setup_complete_data = SetupCompleteData{ + antithesis_setup + }; + + internal::dispatch_output(&setup_complete_data) } /// Causes an event with the name and details provided, @@ -43,7 +59,7 @@ mod tests { #[test] fn setup_complete_without_details() { - eprintln!("setrup_complete"); + eprintln!("setup_complete"); let details: Value = json!({}); setup_complete(&details); assert!(true) diff --git a/lib/src/prelude.rs b/lib/src/prelude.rs index b1a4446..3232a55 100644 --- a/lib/src/prelude.rs +++ b/lib/src/prelude.rs @@ -1,7 +1,7 @@ pub use crate::{antithesis_init, lifecycle, random}; -pub use crate::always as assert_always; -pub use crate::always_or_unreachable as assert_always_or_unreachable; -pub use crate::sometimes as assert_sometimes; -pub use crate::reachable as assert_reachable; -pub use crate::unreachable as assert_unreachable; -pub use crate::{assert_raw}; +pub use crate::assert_always; +pub use crate::assert_always_or_unreachable; +pub use crate::assert_sometimes; +pub use crate::assert_reachable; +pub use crate::assert_unreachable; +pub use crate::assert::{assert_raw, AssertType}; diff --git a/lib/tests/setup_complete_without_details.rs b/lib/tests/setup_complete_without_details.rs index 64d4229..4f8f5f6 100644 --- a/lib/tests/setup_complete_without_details.rs +++ b/lib/tests/setup_complete_without_details.rs @@ -11,7 +11,7 @@ const LOCAL_OUTPUT: &str = "ANTITHESIS_SDK_LOCAL_OUTPUT"; // File: /tmp/antithesis-lifecycle.json // ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── // {"antithesis_sdk":{"language":{"name":"Rust","version":"1.77.1"},"protocol_version":"1.0.0","sdk_version":"0.1.1"}} -// {"antithesis_setup":{"details":{"age":4,"name":"Tweety Bird","phones":["+1 9734970340"]},"status":"complete"}} +// {"antithesis_setup":{"details":{},"status":"complete"}} // ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #[test] fn setup_complete_without_details() { diff --git a/simple/src/main.rs b/simple/src/main.rs index 9589319..d1094c1 100644 --- a/simple/src/main.rs +++ b/simple/src/main.rs @@ -43,6 +43,8 @@ fn lifecycle_demo() { ] }); + let tiger: Value = json!(2457); + lifecycle::setup_complete(&tiger); lifecycle::setup_complete(&bird_value); lifecycle::setup_complete(&cat_value);