diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index ae2b71f..cd816e3 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -63,3 +63,15 @@ jobs: uses: taiki-e/install-action@cargo-hack - name: cargo hack run: cargo hack --feature-powerset --no-dev-deps check + + doc: + runs-on: ubuntu-latest + name: nightly / doc + steps: + - uses: actions/checkout@v4 + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: Install cargo-docs-rs + uses: dtolnay/install@cargo-docs-rs + - name: cargo docs-rs + run: cargo docs-rs diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs index dbb72e1..dbdc5ec 100644 --- a/examples/diagnosis.rs +++ b/examples/diagnosis.rs @@ -21,9 +21,11 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result= 1600 { - step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + step.add_diagnosis("fan_ok", tv::DiagnosisType::Pass) + .await?; } else { - step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + step.add_diagnosis("fan_low", tv::DiagnosisType::Fail) + .await?; } Ok(TestStatus::Complete) diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index 652e0cf..76284fe 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -26,8 +26,8 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| { async move { - r.add_error_with_details( - &tv::Error::builder("power-fail") + r.add_error_detail( + tv::Error::builder("power-fail") .add_software_info(&sw_info) .build(), ) diff --git a/examples/file.rs b/examples/file.rs index 9fe5743..0188b17 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -14,7 +14,7 @@ use tv::{TestResult, TestStatus}; async fn run_file_step(step: &tv::StartedTestStep) -> Result { let uri = tv::Uri::from_str("file:///root/mem_cfg_log").unwrap(); - step.file("mem_cfg_log", uri).await?; + step.add_file("mem_cfg_log", uri).await?; Ok(TestStatus::Complete) } diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index cfb046a..06c00d5 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -14,8 +14,8 @@ use tv::{TestResult, TestStatus}; async fn step0_measurements(step: &tv::StartedTestStep) -> Result { let fan_speed = step - .add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("fan_speed") + .add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("fan_speed") .unit("rpm") .build(), ) @@ -32,8 +32,8 @@ async fn step0_measurements(step: &tv::StartedTestStep) -> Result Result { - step.add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("temp0") + step.add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("temp0") .unit("C") .build(), ) @@ -41,8 +41,8 @@ async fn step1_measurements(step: &tv::StartedTestStep) -> Result Result Result { let freq0 = step - .add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("freq0") + .add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("freq0") .unit("hz") .build(), ) @@ -69,8 +69,8 @@ async fn step2_measurements(step: &tv::StartedTestStep) -> Result Result { step.add_measurement("temperature", 42.5.into()).await?; - step.add_measurement_with_details( - &tv::Measurement::builder("fan_speed", 1200.into()) + step.add_measurement_detail( + tv::Measurement::builder("fan_speed", 1200.into()) .unit("rpm") .build(), ) diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 515f5c4..63b14e0 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -15,21 +15,21 @@ async fn run_measure_step( step: &tv::StartedTestStep, ram0: tv::DutHardwareInfo, ) -> Result { - step.add_measurement_with_details( - &tv::Measurement::builder("temp0", 100.5.into()) + step.add_measurement_detail( + tv::Measurement::builder("temp0", 100.5.into()) .unit("F") .hardware_info(&ram0) - .subcomponent(&tv::Subcomponent::builder("chip0").build()) + .subcomponent(tv::Subcomponent::builder("chip0").build()) .build(), ) .await?; - let chip1_temp = step.add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("temp1") + let chip1_temp = step.add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("temp1") .unit("C") .hardware_info(&ram0) .subcomponent( - &tv::Subcomponent::builder("chip1") + tv::Subcomponent::builder("chip1") .location("U11") .version("1") .revision("1") @@ -59,7 +59,7 @@ async fn run_measure_step( async fn main() -> Result<()> { let mut dut = tv::DutInfo::builder("dut0") .name("host0.example.com") - .add_platform_info(&tv::PlatformInfo::new("memory-optimized")) + .add_platform_info(tv::PlatformInfo::new("memory-optimized")) .build(); dut.add_software_info( diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index 54202c6..3b71e94 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -12,10 +12,10 @@ use ocptv::output as tv; use tv::{TestResult, TestStatus, ValidatorType}; async fn run_measure_step(step: &tv::StartedTestStep) -> Result { - step.add_measurement_with_details( - &tv::Measurement::builder("temp", 40.into()) + step.add_measurement_detail( + tv::Measurement::builder("temp", 40.into()) .add_validator( - &tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) + tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) .name("gt_30") .build(), ) @@ -23,11 +23,11 @@ async fn run_measure_step(step: &tv::StartedTestStep) -> Result Result ConfigBuilder { @@ -47,7 +46,7 @@ impl ConfigBuilder { } } - // TODO: docs for all these + /// TODO: docs for all these pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { self.timestamp_provider = Box::new(ConfiguredTzProvider { tz: timezone }); self @@ -92,6 +91,7 @@ impl ConfigBuilder { } } +/// TODO: docs pub trait TimestampProvider { fn now(&self) -> chrono::DateTime; } diff --git a/src/output/diagnosis.rs b/src/output/diagnosis.rs index f9b59ed..9333427 100644 --- a/src/output/diagnosis.rs +++ b/src/output/diagnosis.rs @@ -9,7 +9,8 @@ use crate::spec; use tv::dut; /// This structure represents a Diagnosis message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis +/// +/// ref: /// /// Information about the source file and line number are not automatically added. /// Add them using the builder or the macros octptv_diagnosis_* @@ -20,7 +21,6 @@ use tv::dut; /// /// ``` /// # use ocptv::output::*; -/// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// ``` /// @@ -28,7 +28,6 @@ use tv::dut; /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -73,7 +72,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -94,7 +92,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; - /// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// let _ = diagnosis.to_artifact(); /// ``` @@ -122,7 +119,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -146,16 +142,7 @@ pub struct DiagnosisBuilder { } impl DiagnosisBuilder { - /// Creates a new DiagnosisBuilder. - /// - /// # Examples - /// - /// ``` - /// # use ocptv::output::*; - /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); - /// ``` - pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { + fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { DiagnosisBuilder { verdict: verdict.to_owned(), diagnosis_type, @@ -169,59 +156,55 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .message("message"); /// ``` - pub fn message(mut self, message: &str) -> DiagnosisBuilder { + pub fn message(mut self, message: &str) -> Self { self.message = Some(message.to_owned()); self } - /// Add a [`HardwareInfo`] to a [`DiagnosisBuilder`]. + /// Add a [`dut::HardwareInfo`] to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .hardware_info(&hw_info); /// ``` - pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> DiagnosisBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } - /// Add a [`Subcomponent`] to a [`DiagnosisBuilder`]. + /// Add a [`dut::Subcomponent`] to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .subcomponent(&Subcomponent::builder("name").build()); /// ``` - pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> DiagnosisBuilder { + pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> Self { self.subcomponent = Some(subcomponent.clone()); self } - /// Add a [`SourceLocation`] to a [`DiagnosisBuilder`]. + /// Add a source location to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .source("file.rs", 1); /// ``` - pub fn source(mut self, file: &str, line: i32) -> DiagnosisBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_owned(), line, @@ -235,8 +218,7 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass); /// let diagnosis = builder.build(); /// ``` pub fn build(self) -> Diagnosis { diff --git a/src/output/dut.rs b/src/output/dut.rs index ed8777a..f49a99e 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -4,14 +4,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use maplit::{btreemap, convert_args}; use std::collections::BTreeMap; use crate::output as tv; +use crate::output::trait_ext::{MapExt, VecExt}; use crate::spec; -use tv::trait_ext::VecExt; -// TODO: docs +/// TODO: docs #[derive(Clone, Debug, PartialEq, Default)] pub enum Ident { #[default] @@ -29,7 +28,7 @@ pub struct DutInfo { software_infos: Vec, hardware_infos: Vec, - metadata: Option>, + metadata: BTreeMap, } impl DutInfo { @@ -78,47 +77,40 @@ impl DutInfo { platform_infos: self.platform_infos.map_option(PlatformInfo::to_spec), software_infos: self.software_infos.map_option(DutSoftwareInfo::to_spec), hardware_infos: self.hardware_infos.map_option(DutHardwareInfo::to_spec), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } +/// TODO: docs #[derive(Default)] pub struct DutInfoBuilder { id: String, name: Option, platform_infos: Vec, - metadata: Option>, + metadata: BTreeMap, } impl DutInfoBuilder { - pub fn new(id: &str) -> DutInfoBuilder { + fn new(id: &str) -> Self { DutInfoBuilder { id: id.to_string(), ..Default::default() } } - pub fn name(mut self, value: &str) -> DutInfoBuilder { + pub fn name(mut self, value: &str) -> Self { self.name = Some(value.to_string()); self } - pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { - self.platform_infos.push(platform_info.clone()); + pub fn add_platform_info(mut self, platform_info: PlatformInfo) -> Self { + self.platform_infos.push(platform_info); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value - ))), - }; + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata.insert(key.to_string(), value); self } @@ -133,6 +125,7 @@ impl DutInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct Subcomponent { subcomponent_type: Option, @@ -157,6 +150,7 @@ impl Subcomponent { } } +/// TODO: docs #[derive(Debug)] pub struct SubcomponentBuilder { subcomponent_type: Option, @@ -176,19 +170,19 @@ impl SubcomponentBuilder { revision: None, } } - pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> SubcomponentBuilder { + pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> Self { self.subcomponent_type = Some(value); self } - pub fn version(mut self, value: &str) -> SubcomponentBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn location(mut self, value: &str) -> SubcomponentBuilder { + pub fn location(mut self, value: &str) -> Self { self.location = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> SubcomponentBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } @@ -204,6 +198,7 @@ impl SubcomponentBuilder { } } +/// TODO: docs #[derive(Debug, Clone, PartialEq)] pub struct PlatformInfo { info: String, @@ -227,6 +222,7 @@ impl PlatformInfo { } } +/// TODO: docs #[derive(Debug)] pub struct PlatformInfoBuilder { info: String, @@ -244,6 +240,7 @@ impl PlatformInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct SoftwareInfo { id: tv::Ident, @@ -260,6 +257,7 @@ impl SoftwareInfo { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct DutSoftwareInfo { id: String, @@ -287,6 +285,7 @@ impl PartialEq for DutSoftwareInfo { } } +/// TODO: docs #[derive(Debug, Default)] pub struct SoftwareInfoBuilder { id: tv::Ident, @@ -306,27 +305,27 @@ impl SoftwareInfoBuilder { } } - pub fn id(mut self, value: tv::Ident) -> SoftwareInfoBuilder { + pub fn id(mut self, value: tv::Ident) -> Self { self.id = value; self } - pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } - pub fn software_type(mut self, value: spec::SoftwareType) -> SoftwareInfoBuilder { + pub fn software_type(mut self, value: spec::SoftwareType) -> Self { self.software_type = Some(value); self } - pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn computer_system(mut self, value: &str) -> Self { self.computer_system = Some(value.to_string()); self } @@ -343,6 +342,7 @@ impl SoftwareInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct HardwareInfo { id: Ident, @@ -367,6 +367,7 @@ impl HardwareInfo { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct DutHardwareInfo { id: String, @@ -400,6 +401,7 @@ impl PartialEq for DutHardwareInfo { } } +/// TODO: docs #[derive(Debug, Default)] pub struct HardwareInfoBuilder { id: tv::Ident, @@ -426,57 +428,57 @@ impl HardwareInfoBuilder { } } - pub fn id(mut self, value: tv::Ident) -> HardwareInfoBuilder { + pub fn id(mut self, value: tv::Ident) -> Self { self.id = value; self } - pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } - pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + pub fn location(mut self, value: &str) -> Self { self.location = Some(value.to_string()); self } - pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn serial_no(mut self, value: &str) -> Self { self.serial_no = Some(value.to_string()); self } - pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn part_no(mut self, value: &str) -> Self { self.part_no = Some(value.to_string()); self } - pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manufacturer(mut self, value: &str) -> Self { self.manufacturer = Some(value.to_string()); self } - pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manufacturer_part_no(mut self, value: &str) -> Self { self.manufacturer_part_no = Some(value.to_string()); self } - pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + pub fn odata_id(mut self, value: &str) -> Self { self.odata_id = Some(value.to_string()); self } - pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + pub fn computer_system(mut self, value: &str) -> Self { self.computer_system = Some(value.to_string()); self } - pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manager(mut self, value: &str) -> Self { self.manager = Some(value.to_string()); self } @@ -518,7 +520,7 @@ mod tests { .name("dut") .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) - .add_platform_info(&PlatformInfo::builder("platform_info").build()) + .add_platform_info(PlatformInfo::builder("platform_info").build()) .build(); dut.add_software_info( diff --git a/src/output/error.rs b/src/output/error.rs index ab5fa61..764a96c 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -8,6 +8,8 @@ use crate::output as tv; use crate::spec; use tv::{dut, trait_ext::VecExt, DutSoftwareInfo}; +/// TODO: docs +#[derive(Clone)] pub struct Error { symptom: String, message: Option, @@ -30,6 +32,7 @@ impl Error { } } +/// TODO: docs #[derive(Debug, Default)] pub struct ErrorBuilder { symptom: String, @@ -46,12 +49,12 @@ impl ErrorBuilder { } } - pub fn message(mut self, value: &str) -> ErrorBuilder { + pub fn message(mut self, value: &str) -> Self { self.message = Some(value.to_string()); self } - pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, @@ -59,7 +62,7 @@ impl ErrorBuilder { self } - pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> ErrorBuilder { + pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> Self { self.software_infos.push(software_info.clone()); self } diff --git a/src/output/file.rs b/src/output/file.rs index c95ce4b..dab09f7 100644 --- a/src/output/file.rs +++ b/src/output/file.rs @@ -4,15 +4,16 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use mime; use std::collections::BTreeMap; -use crate::output as tv; +use mime; + +use crate::output::{self as tv, trait_ext::MapExt}; use crate::spec; -use maplit::{btreemap, convert_args}; /// This structure represents a File message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file +/// +/// ref: /// /// # Examples /// @@ -20,7 +21,6 @@ use maplit::{btreemap, convert_args}; /// /// ``` /// # use ocptv::output::*; -/// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::new("name", uri); /// ``` @@ -30,7 +30,6 @@ use maplit::{btreemap, convert_args}; /// ``` /// # use ocptv::output::*; /// # use std::str::FromStr; -/// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .is_snapshot(true) @@ -45,7 +44,7 @@ pub struct File { is_snapshot: bool, description: Option, content_type: Option, - metadata: Option>, + metadata: BTreeMap, } impl File { @@ -55,7 +54,6 @@ impl File { /// /// ``` /// # use ocptv::output::*; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::new("name", uri); /// ``` @@ -66,7 +64,7 @@ impl File { is_snapshot: false, description: None, content_type: None, - metadata: None, + metadata: BTreeMap::new(), } } @@ -77,7 +75,6 @@ impl File { /// ``` /// # use ocptv::output::*; /// # use std::str::FromStr; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .description("description") @@ -95,7 +92,6 @@ impl File { /// /// ``` /// # use ocptv::output::*; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::new("name", uri); /// let _ = file.to_artifact(); @@ -107,7 +103,7 @@ impl File { is_snapshot: self.is_snapshot, description: self.description.clone(), content_type: self.content_type.as_ref().map(|ct| ct.to_string()), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } @@ -119,7 +115,6 @@ impl File { /// ``` /// # use ocptv::output::*; /// # use std::str::FromStr; -/// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = File::builder("name", uri) /// .description("description") @@ -133,28 +128,19 @@ pub struct FileBuilder { is_snapshot: bool, description: Option, content_type: Option, - metadata: Option>, + + metadata: BTreeMap, } impl FileBuilder { - /// Creates a new FileBuilder. - /// - /// # Examples - /// - /// ``` - /// # use ocptv::output::*; - /// - /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri); - /// ``` - pub fn new(name: &str, uri: tv::Uri) -> Self { + fn new(name: &str, uri: tv::Uri) -> Self { FileBuilder { name: name.to_string(), uri, is_snapshot: false, description: None, content_type: None, - metadata: None, + metadata: BTreeMap::new(), } } @@ -164,9 +150,8 @@ impl FileBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri) + /// let builder = File::builder("name", uri) /// .is_snapshot(true); /// ``` pub fn is_snapshot(mut self, value: bool) -> FileBuilder { @@ -180,9 +165,8 @@ impl FileBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri) + /// let builder = File::builder("name", uri) /// .description("description"); /// ``` pub fn description(mut self, description: &str) -> FileBuilder { @@ -197,9 +181,8 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// # use std::str::FromStr; - /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri) + /// let builder = File::builder("name", uri) /// .content_type(mime::TEXT_PLAIN); /// ``` pub fn content_type(mut self, content_type: mime::Mime) -> FileBuilder { @@ -215,20 +198,11 @@ impl FileBuilder { /// # use ocptv::output::*; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri) + /// let builder = File::builder("name", uri) /// .add_metadata("key", "value".into()); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> FileBuilder { - match self.metadata { - Some(ref mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - } - None => { - self.metadata = Some(convert_args!(btreemap!( - key => value, - ))); - } - }; + self.metadata.insert(key.to_string(), value); self } @@ -240,7 +214,7 @@ impl FileBuilder { /// # use ocptv::output::*; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// let builder = FileBuilder::new("name", uri); + /// let builder = File::builder("name", uri); /// let file = builder.build(); /// ``` pub fn build(self) -> File { @@ -261,6 +235,8 @@ mod tests { use crate::output as tv; use crate::spec; use anyhow::Result; + use maplit::btreemap; + use maplit::convert_args; #[test] fn test_file_as_test_step_descendant_to_artifact() -> Result<()> { diff --git a/src/output/log.rs b/src/output/log.rs index baf8635..f39ad90 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -6,6 +6,7 @@ use crate::spec; +/// TODO: docs pub struct Log { severity: spec::LogSeverity, message: String, @@ -26,6 +27,7 @@ impl Log { } } +/// TODO: docs #[derive(Debug)] pub struct LogBuilder { severity: spec::LogSeverity, @@ -41,11 +43,11 @@ impl LogBuilder { source_location: None, } } - pub fn severity(mut self, value: spec::LogSeverity) -> LogBuilder { + pub fn severity(mut self, value: spec::LogSeverity) -> Self { self.severity = value; self } - pub fn source(mut self, file: &str, line: i32) -> LogBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, diff --git a/src/output/macros.rs b/src/output/macros.rs index ba4196c..7e66146 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -9,10 +9,11 @@ //! This module contains a set of macros which are exported from the ocptv //! library. -/// Emits an artifact of type Error. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error +/// Emit an artifact of type Error. /// -/// Equivalent to the crate::runner::TestRun::error_with_details method. +/// ref: +/// +/// Equivalent to the [`$crate::StartedTestRun::error_with_details`] method. /// /// It accepts both a symptom and a message, or just a symptom. /// Information about the source file and line number is automatically added. @@ -24,7 +25,6 @@ /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; -/// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -55,8 +55,8 @@ #[macro_export] macro_rules! ocptv_error { ($runner:expr, $symptom:expr, $msg:expr) => { - $runner.add_error_with_details( - &$crate::output::Error::builder($symptom) + $runner.add_error_detail( + $crate::output::Error::builder($symptom) .message($msg) .source(file!(), line!() as i32) .build(), @@ -64,51 +64,50 @@ macro_rules! ocptv_error { }; ($runner:expr, $symptom:expr) => { - $runner.add_error_with_details( - &$crate::output::Error::builder($symptom) + $runner.add_error_detail( + $crate::output::Error::builder($symptom) .source(file!(), line!() as i32) .build(), ) }; } -/// The following macros emit an artifact of type Log. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log -/// -/// Equivalent to the crate::runner::TestRun::log_with_details method. -/// -/// They accept message as only parameter. -/// Information about the source file and line number is automatically added. -/// -/// There is one macro for each severity level: DEBUG, INFO, WARNING, ERROR, and FATAL. -/// -/// # Examples -/// -/// ## DEBUG -/// -/// ```rust -/// # tokio_test::block_on(async { -/// # use ocptv::output::*; -/// -/// use ocptv::ocptv_log_debug; -/// -/// let dut = DutInfo::new("my_dut"); -/// let run = TestRun::new("run_name", "1.0").start(dut).await?; -/// ocptv_log_debug!(run, "Log message"); -/// run.end(TestStatus::Complete, TestResult::Pass).await?; -/// -/// # Ok::<(), OcptvError>(()) -/// # }); -/// ``` - macro_rules! ocptv_log { - ($name:ident, $severity:ident) => { + ($name:ident, $severity:path) => { + /// Emit an artifact of type Log. + /// + /// ref: + /// + /// Equivalent to the [`$crate::StartedTestRun::log_with_details`] method. + /// + /// They accept message as only parameter. + /// Information about the source file and line number is automatically added. + /// + /// There is one macro for each severity level: DEBUG, INFO, WARNING, ERROR, and FATAL. + /// + /// # Examples + /// + /// ## DEBUG + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// use ocptv::ocptv_log_debug; + /// + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("run_name", "1.0").start(dut).await?; + /// ocptv_log_debug!(run, "Log message"); + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` #[macro_export] macro_rules! $name { ($artifact:expr, $msg:expr) => { - $artifact.add_log_with_details( - &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::$severity) + $artifact.add_log_detail( + $crate::output::Log::builder($msg) + .severity($severity) .source(file!(), line!() as i32) .build(), ) @@ -117,52 +116,51 @@ macro_rules! ocptv_log { }; } -ocptv_log!(ocptv_log_debug, Debug); -ocptv_log!(ocptv_log_info, Info); -ocptv_log!(ocptv_log_warning, Warning); -ocptv_log!(ocptv_log_error, Error); -ocptv_log!(ocptv_log_fatal, Fatal); - -/// The following macros emit an artifact of type Diagnosis. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis -/// -/// Equivalent to the crate::output::StartedTestStep::diagnosis_with_details method. -/// -/// They accept verdict as only parameter. -/// Information about the source file and line number is automatically added. -/// -/// There is one macro for each DiagnosisType variant: Pass, Fail, Unknown. -/// -/// # Examples -/// -/// ## DEBUG -/// -/// ```rust -/// # tokio_test::block_on(async { -/// # use ocptv::output::*; -/// -/// use ocptv::ocptv_diagnosis_pass; -/// -/// let dut = DutInfo::new("my dut"); -/// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; -/// -/// let step = run.add_step("step_name").start().await?; -/// ocptv_diagnosis_pass!(step, "verdict"); -/// step.end(TestStatus::Complete).await?; -/// -/// run.end(TestStatus::Complete, TestResult::Pass).await?; -/// -/// # Ok::<(), OcptvError>(()) -/// # }); -/// ``` +ocptv_log!(ocptv_log_debug, ocptv::output::LogSeverity::Debug); +ocptv_log!(ocptv_log_info, ocptv::output::LogSeverity::Info); +ocptv_log!(ocptv_log_warning, ocptv::output::LogSeverity::Warning); +ocptv_log!(ocptv_log_error, ocptv::output::LogSeverity::Error); +ocptv_log!(ocptv_log_fatal, ocptv::output::LogSeverity::Fatal); macro_rules! ocptv_diagnosis { ($name:ident, $diagnosis_type:path) => { + /// Emit an artifact of type Diagnosis. + /// + /// ref: + /// + /// Equivalent to the [`$crate::StartedTestStep::diagnosis_with_details`] method. + /// + /// They accept verdict as only parameter. + /// Information about the source file and line number is automatically added. + /// + /// There is one macro for each DiagnosisType variant: Pass, Fail, Unknown. + /// + /// # Examples + /// + /// ## DEBUG + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// use ocptv::ocptv_diagnosis_pass; + /// + /// let dut = DutInfo::new("my dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// + /// let step = run.add_step("step_name").start().await?; + /// ocptv_diagnosis_pass!(step, "verdict"); + /// step.end(TestStatus::Complete).await?; + /// + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` #[macro_export] macro_rules! $name { ($artifact:expr, $verdict:expr) => { - $artifact.diagnosis_with_details( - &$crate::output::Diagnosis::builder($verdict, $diagnosis_type) + $artifact.add_diagnosis_detail( + $crate::output::Diagnosis::builder($verdict, $diagnosis_type) .source(file!(), line!() as i32) .build(), ) diff --git a/src/output/measure.rs b/src/output/measure.rs index 4661945..11620de 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -10,21 +10,19 @@ use std::sync::Arc; #[cfg(feature = "boxed-scopes")] use futures::future::BoxFuture; -use maplit::{btreemap, convert_args}; use crate::output as tv; +use crate::output::trait_ext::{MapExt, VecExt}; use crate::spec; use tv::{dut, step, Ident}; -use super::trait_ext::VecExt; - /// The measurement series. /// A Measurement Series is a time-series list of measurements. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +/// ref: pub struct MeasurementSeries { id: String, - info: MeasurementSeriesInfo, + detail: MeasurementSeriesDetail, emitter: Arc, } @@ -34,26 +32,25 @@ impl MeasurementSeries { // instances through the `StartedTestStep.add_measurement_series_*` apis pub(crate) fn new( series_id: &str, - info: MeasurementSeriesInfo, + info: MeasurementSeriesDetail, emitter: Arc, ) -> Self { Self { id: series_id.to_owned(), - info, + detail: info, emitter, } } /// Starts the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -65,7 +62,7 @@ impl MeasurementSeries { /// # }); /// ``` pub async fn start(self) -> Result { - let info = &self.info; + let info = &self.detail; let start = spec::MeasurementSeriesStart { name: info.name.clone(), @@ -77,7 +74,7 @@ impl MeasurementSeries { .as_ref() .map(dut::DutHardwareInfo::to_spec), subcomponent: info.subcomponent.as_ref().map(dut::Subcomponent::to_spec), - metadata: info.metadata.clone(), + metadata: info.metadata.option(), }; self.emitter @@ -91,7 +88,7 @@ impl MeasurementSeries { } /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// ending it. View [`MeasurementSeries::start`] and [`StartedMeasurementSeries::end`] methods. /// After the scope is constructed, additional objects may be added to it. /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees /// all the messages are emitted between the start and end messages, the order @@ -103,7 +100,6 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -134,6 +130,7 @@ impl MeasurementSeries { } } +/// TODO: docs pub struct StartedMeasurementSeries { parent: MeasurementSeries, @@ -147,14 +144,13 @@ impl StartedMeasurementSeries { /// Ends the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -181,14 +177,13 @@ impl StartedMeasurementSeries { /// Adds a measurement element to the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -200,7 +195,7 @@ impl StartedMeasurementSeries { /// # }); /// ``` pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError> { - self.add_measurement_with_details(MeasurementSeriesElemDetails { + self.add_measurement_detail(MeasurementElementDetail { value, ..Default::default() }) @@ -210,37 +205,36 @@ impl StartedMeasurementSeries { /// Adds a measurement element to the measurement series. /// This method accepts a full set of details for the measurement element. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; - /// let elem = MeasurementSeriesElemDetails::builder(60.into()).add_metadata("key", "value".into()).build(); - /// series.add_measurement_with_details(elem).await?; + /// let elem = MeasurementElementDetail::builder(60.into()).add_metadata("key", "value".into()).build(); + /// series.add_measurement_detail(elem).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement_with_details( + pub async fn add_measurement_detail( &self, - details: MeasurementSeriesElemDetails, + element: MeasurementElementDetail, ) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), - value: details.value, - timestamp: details + value: element.value, + timestamp: element .timestamp .unwrap_or(self.parent.emitter.timestamp_provider().now()), series_id: self.parent.id.clone(), - metadata: details.metadata, + metadata: element.metadata.option(), }; self.parent @@ -254,30 +248,32 @@ impl StartedMeasurementSeries { } } +/// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesElemDetails { +pub struct MeasurementElementDetail { value: tv::Value, timestamp: Option>, - metadata: Option>, + metadata: BTreeMap, } -impl MeasurementSeriesElemDetails { - pub fn builder(value: tv::Value) -> MeasurementSeriesElemDetailsBuilder { - MeasurementSeriesElemDetailsBuilder::new(value) +impl MeasurementElementDetail { + pub fn builder(value: tv::Value) -> MeasurementElementDetailBuilder { + MeasurementElementDetailBuilder::new(value) } } +/// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesElemDetailsBuilder { +pub struct MeasurementElementDetailBuilder { value: tv::Value, timestamp: Option>, - metadata: Option>, + metadata: BTreeMap, } -impl MeasurementSeriesElemDetailsBuilder { - pub fn new(value: tv::Value) -> Self { +impl MeasurementElementDetailBuilder { + fn new(value: tv::Value) -> Self { Self { value, ..Default::default() @@ -290,20 +286,12 @@ impl MeasurementSeriesElemDetailsBuilder { } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + self.metadata.insert(key.to_string(), value); self } - pub fn build(self) -> MeasurementSeriesElemDetails { - MeasurementSeriesElemDetails { + pub fn build(self) -> MeasurementElementDetail { + MeasurementElementDetail { value: self.value, timestamp: self.timestamp, metadata: self.metadata, @@ -311,59 +299,57 @@ impl MeasurementSeriesElemDetailsBuilder { } } +/// TODO: docs #[derive(Clone)] pub struct Validator { name: Option, validator_type: spec::ValidatorType, value: tv::Value, - metadata: Option>, + metadata: BTreeMap, } impl Validator { pub fn builder(validator_type: spec::ValidatorType, value: tv::Value) -> ValidatorBuilder { ValidatorBuilder::new(validator_type, value) } + pub fn to_spec(&self) -> spec::Validator { spec::Validator { name: self.name.clone(), validator_type: self.validator_type.clone(), value: self.value.clone(), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } +/// TODO: docs #[derive(Debug)] pub struct ValidatorBuilder { name: Option, validator_type: spec::ValidatorType, value: tv::Value, - metadata: Option>, + + metadata: BTreeMap, } impl ValidatorBuilder { fn new(validator_type: spec::ValidatorType, value: tv::Value) -> Self { ValidatorBuilder { validator_type, - value: value.clone(), + value, name: None, - metadata: None, + metadata: BTreeMap::new(), } } - pub fn name(mut self, value: &str) -> ValidatorBuilder { + + pub fn name(mut self, value: &str) -> Self { self.name = Some(value.to_string()); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> ValidatorBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata.insert(key.to_string(), value); self } @@ -378,16 +364,14 @@ impl ValidatorBuilder { } /// This structure represents a Measurement message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// ref: /// /// # Examples /// /// ## Create a Measurement object with the `new` method /// /// ``` -/// use ocptv::output::Measurement; -/// use ocptv::output::Value; -/// +/// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// ``` /// @@ -395,28 +379,28 @@ impl ValidatorBuilder { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) -/// .subcomponent(&Subcomponent::builder("name").build()) +/// .subcomponent(Subcomponent::builder("name").build()) /// .build(); /// ``` +#[derive(Default)] pub struct Measurement { name: String, value: tv::Value, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl Measurement { @@ -425,20 +409,14 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// ``` pub fn new(name: &str, value: tv::Value) -> Self { Measurement { name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, + value, + ..Default::default() } } @@ -453,10 +431,10 @@ impl Measurement { /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) - /// .subcomponent(&Subcomponent::builder("name").build()) + /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); /// ``` pub fn builder(name: &str, value: tv::Value) -> MeasurementBuilder { @@ -468,9 +446,7 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` @@ -479,10 +455,7 @@ impl Measurement { name: self.name.clone(), unit: self.unit.clone(), value: self.value.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + validators: self.validators.map_option(Validator::to_spec), hardware_info: self .hardware_info .as_ref() @@ -491,7 +464,7 @@ impl Measurement { .subcomponent .as_ref() .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } @@ -502,50 +475,36 @@ impl Measurement { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// -/// let builder = MeasurementBuilder::new("name", 50.into()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// let builder = Measurement::builder("name", 50.into()) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) -/// .subcomponent(&Subcomponent::builder("name").build()); +/// .subcomponent(Subcomponent::builder("name").build()); /// let measurement = builder.build(); /// ``` +#[derive(Default)] pub struct MeasurementBuilder { name: String, value: tv::Value, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementBuilder { - /// Creates a new MeasurementBuilder. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); - /// ``` - pub fn new(name: &str, value: tv::Value) -> Self { + fn new(name: &str, value: tv::Value) -> Self { MeasurementBuilder { name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, + value, + ..Default::default() } } @@ -555,53 +514,42 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); + /// let builder = Measurement::builder("name", 50.into()) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()); /// ``` - pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; + pub fn add_validator(mut self, validator: Validator) -> Self { + self.validators.push(validator.clone()); self } - /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. + /// Add a [`tv::HardwareInfo`] to a [`MeasurementBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let builder = MeasurementBuilder::new("name", 50.into()) + /// let builder = Measurement::builder("name", 50.into()) /// .hardware_info(&hw_info); /// ``` - pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> MeasurementBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } - /// Add a [`Subcomponent`] to a [`MeasurementBuilder`]. + /// Add a [`tv::Subcomponent`] to a [`MeasurementBuilder`]. /// /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .subcomponent(&Subcomponent::builder("name").build()); + /// # use ocptv::output::*; + /// let builder = Measurement::builder("name", 50.into()) + /// .subcomponent(Subcomponent::builder("name").build()); /// ``` - pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> MeasurementBuilder { - self.subcomponent = Some(subcomponent.clone()); + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self { + self.subcomponent = Some(subcomponent); self } @@ -610,23 +558,12 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let builder = - /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); + /// Measurement::builder("name", 50.into()).add_metadata("key", "value".into()); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementBuilder { - match self.metadata { - Some(ref mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - } - None => { - self.metadata = Some(convert_args!(btreemap!( - key => value, - ))); - } - }; + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata.insert(key.to_string(), value); self } @@ -635,10 +572,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); + /// # use ocptv::output::*; + /// let builder = Measurement::builder("name", 50000.into()).unit("RPM"); /// ``` pub fn unit(mut self, unit: &str) -> MeasurementBuilder { self.unit = Some(unit.to_string()); @@ -650,10 +585,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); + /// # use ocptv::output::*; + /// let builder = Measurement::builder("name", 50.into()); /// let measurement = builder.build(); /// ``` pub fn build(self) -> Measurement { @@ -669,7 +602,8 @@ impl MeasurementBuilder { } } -pub struct MeasurementSeriesInfo { +/// TODO: docs +pub struct MeasurementSeriesDetail { // note: this object is crate public and we need access to this field // when making a new series in `StartedTestStep.add_measurement_series*` pub(crate) id: tv::Ident, @@ -681,21 +615,22 @@ pub struct MeasurementSeriesInfo { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } -impl MeasurementSeriesInfo { - pub fn new(name: &str) -> MeasurementSeriesInfo { - MeasurementSeriesInfoBuilder::new(name).build() +impl MeasurementSeriesDetail { + pub fn new(name: &str) -> MeasurementSeriesDetail { + MeasurementSeriesDetailBuilder::new(name).build() } - pub fn builder(name: &str) -> MeasurementSeriesInfoBuilder { - MeasurementSeriesInfoBuilder::new(name) + pub fn builder(name: &str) -> MeasurementSeriesDetailBuilder { + MeasurementSeriesDetailBuilder::new(name) } } +/// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesInfoBuilder { +pub struct MeasurementSeriesDetailBuilder { id: tv::Ident, name: String, @@ -705,64 +640,50 @@ pub struct MeasurementSeriesInfoBuilder { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } -impl MeasurementSeriesInfoBuilder { - pub fn new(name: &str) -> Self { - MeasurementSeriesInfoBuilder { +impl MeasurementSeriesDetailBuilder { + fn new(name: &str) -> Self { + MeasurementSeriesDetailBuilder { id: Ident::Auto, name: name.to_string(), ..Default::default() } } - pub fn id(mut self, id: tv::Ident) -> MeasurementSeriesInfoBuilder { + pub fn id(mut self, id: tv::Ident) -> Self { self.id = id; self } - pub fn unit(mut self, unit: &str) -> MeasurementSeriesInfoBuilder { + pub fn unit(mut self, unit: &str) -> Self { self.unit = Some(unit.to_string()); self } - pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesInfoBuilder { - self.validators.push(validator.clone()); + pub fn add_validator(mut self, validator: Validator) -> Self { + self.validators.push(validator); self } - pub fn hardware_info( - mut self, - hardware_info: &dut::DutHardwareInfo, - ) -> MeasurementSeriesInfoBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } - pub fn subcomponent( - mut self, - subcomponent: &dut::Subcomponent, - ) -> MeasurementSeriesInfoBuilder { - self.subcomponent = Some(subcomponent.clone()); + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self { + self.subcomponent = Some(subcomponent); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value - ))), - }; + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata.insert(key.to_string(), value); self } - pub fn build(self) -> MeasurementSeriesInfo { - MeasurementSeriesInfo { + pub fn build(self) -> MeasurementSeriesDetail { + MeasurementSeriesDetail { id: self.id, name: self.name, unit: self.unit, @@ -779,6 +700,7 @@ mod tests { use super::*; use crate::output as tv; use crate::spec; + use maplit::{btreemap, convert_args}; use tv::dut::*; use tv::ValidatorType; @@ -827,10 +749,10 @@ mod tests { let unit = "RPM"; let measurement = Measurement::builder(&name, value.clone()) .unit(unit) - .add_validator(&validator) - .add_validator(&validator) + .add_validator(validator.clone()) + .add_validator(validator.clone()) .hardware_info(&hw_info) - .subcomponent(&subcomponent) + .subcomponent(subcomponent.clone()) .add_metadata(meta_key, meta_value.clone()) .build(); diff --git a/src/output/mod.rs b/src/output/mod.rs index 7687b2b..83e865e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -34,9 +34,9 @@ pub use error::{Error, ErrorBuilder}; pub use file::{File, FileBuilder}; pub use log::{Log, LogBuilder}; pub use measure::{ - Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesElemDetails, - MeasurementSeriesInfo, MeasurementSeriesInfoBuilder, StartedMeasurementSeries, Validator, - ValidatorBuilder, + Measurement, MeasurementBuilder, MeasurementElementDetail, MeasurementElementDetailBuilder, + MeasurementSeries, MeasurementSeriesDetail, MeasurementSeriesDetailBuilder, + StartedMeasurementSeries, Validator, ValidatorBuilder, }; pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; pub use step::{StartedTestStep, TestStep}; @@ -46,6 +46,7 @@ pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; pub use serde_json::Value; pub use url::Url as Uri; +// TODO: docs #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum OcptvError { diff --git a/src/output/run.rs b/src/output/run.rs index e91ed4a..3b5856d 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -13,13 +13,13 @@ use std::sync::{ Arc, }; -use maplit::{btreemap, convert_args}; - use crate::output as tv; use crate::spec; use tv::step::TestStep; use tv::{config, dut, emitter, error, log}; +use super::trait_ext::MapExt; + /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. pub struct TestRunOutcome { @@ -37,7 +37,7 @@ pub struct TestRun { version: String, parameters: BTreeMap, command_line: String, - metadata: Option>, + metadata: BTreeMap, emitter: Arc, } @@ -49,7 +49,6 @@ impl TestRun { /// /// ```rust /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// ``` pub fn new(name: &str, version: &str) -> TestRun { @@ -62,7 +61,6 @@ impl TestRun { /// /// ```rust /// # use ocptv::output::*; - /// /// let builder = TestRun::builder("run_name", "1.0"); /// ``` pub fn builder(name: &str, version: &str) -> TestRunBuilder { @@ -71,15 +69,13 @@ impl TestRun { /// Starts the test run. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// let dut = DutInfo::builder("my_dut").build(); /// run.start(dut).await?; @@ -94,7 +90,7 @@ impl TestRun { version: self.version.clone(), command_line: self.command_line.clone(), parameters: self.parameters.clone(), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), dut_info: dut.to_spec(), }), }); @@ -117,7 +113,6 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// let dut = DutInfo::builder("my_dut").build(); /// run.scope(dut, |r| { @@ -154,7 +149,7 @@ impl TestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(&error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -163,11 +158,11 @@ impl TestRun { /// This operation is useful in such cases when there is an error before starting the test. /// (eg. failing to discover a DUT). /// - /// See: [`StartedTestRun::add_error_with_msg`] for details and examples. - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + /// See: [`StartedTestRun::add_error_msg`] for details and examples. + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(&error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -176,8 +171,8 @@ impl TestRun { /// This operation is useful in such cases when there is an error before starting the test. /// (eg. failing to discover a DUT). /// - /// See: [`StartedTestRun::add_error_with_details`] for details and examples. - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + /// See: [`StartedTestRun::add_error_detail`] for details and examples. + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; @@ -190,24 +185,25 @@ impl TestRun { } /// Builder for the [`TestRun`] object. +#[derive(Default)] pub struct TestRunBuilder { name: String, version: String, parameters: BTreeMap, command_line: String, - metadata: Option>, + config: Option, + metadata: BTreeMap, } impl TestRunBuilder { - pub fn new(name: &str, version: &str) -> Self { + fn new(name: &str, version: &str) -> Self { Self { name: name.to_string(), version: version.to_string(), parameters: BTreeMap::new(), command_line: env::args().collect::>()[1..].join(" "), - metadata: None, - config: None, + ..Default::default() } } @@ -217,29 +213,27 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .add_parameter("param1", "value1".into()) /// .build(); /// ``` - pub fn add_parameter(mut self, key: &str, value: tv::Value) -> TestRunBuilder { - self.parameters.insert(key.to_string(), value.clone()); + pub fn add_parameter(mut self, key: &str, value: tv::Value) -> Self { + self.parameters.insert(key.to_string(), value); self } - /// Adds the command line used to run the test session to the future + /// Adds the command line used to run the test session to the future /// [`TestRun`] object. /// /// # Examples /// /// ```rust /// # use ocptv::output::*; - /// - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .command_line("my_diag --arg value") /// .build(); /// ``` - pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + pub fn command_line(mut self, cmd: &str) -> Self { self.command_line = cmd.to_string(); self } @@ -250,12 +244,11 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .config(Config::builder().build()) /// .build(); /// ``` - pub fn config(mut self, value: config::Config) -> TestRunBuilder { + pub fn config(mut self, value: config::Config) -> Self { self.config = Some(value); self } @@ -267,20 +260,12 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .add_metadata("meta1", "value1".into()) /// .build(); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> TestRunBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata.insert(key.to_string(), value); self } @@ -302,7 +287,7 @@ impl TestRunBuilder { /// A test run that was started. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +/// ref: pub struct StartedTestRun { run: TestRun, @@ -319,14 +304,13 @@ impl StartedTestRun { /// Ends the test run. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -348,17 +332,16 @@ impl StartedTestRun { } /// Emits a Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. + /// This method accepts a [`tv::LogSeverity`] to define the severity + /// and a [`String`] for the message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log( @@ -389,20 +372,19 @@ impl StartedTestRun { } /// Emits a Log message. - /// This method accepts a [`objects::Log`] object. + /// This method accepts a [`tv::Log`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; - /// run.add_log_with_details( - /// &Log::builder("This is a log message with INFO severity") + /// run.add_log_detail( + /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), @@ -412,7 +394,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -425,16 +407,15 @@ impl StartedTestRun { } /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom. + /// This method accepts a [`String`] to define the symptom. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_error("symptom").await?; @@ -446,54 +427,52 @@ impl StartedTestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(&error).await?; + self.add_error_detail(error).await?; Ok(()) } /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. + /// This method accepts a [`String`] to define the symptom and + /// another [`String`] as error message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; - /// run.add_error_with_msg("symptom", "error messasge").await?; + /// run.add_error_msg("symptom", "error messasge").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(&error).await?; + self.add_error_detail(error).await?; Ok(()) } /// Emits a Error message. - /// This method accepts an [`error::Error`] object. + /// This method accepts an [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// - /// run.add_error_with_details( - /// &Error::builder("symptom") + /// run.add_error_detail( + /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&sw_info) @@ -505,7 +484,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; diff --git a/src/output/step.rs b/src/output/step.rs index 813a89c..ec27871 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -16,9 +16,9 @@ use crate::spec::{self, TestStepArtifactImpl}; use tv::OcptvError; use tv::{config, diagnosis, emitter, error, file, log, measure, Ident}; -/// A single test step in the scope of a [`TestRun`]. +/// A single test step in the scope of a [`tv::TestRun`]. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +/// ref: pub struct TestStep { name: String, @@ -40,14 +40,13 @@ impl TestStep { /// Starts the test step. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -69,7 +68,7 @@ impl TestStep { } /// Builds a scope in the [`TestStep`] object, taking care of starting and - /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + /// ending it. View [`TestStep::start`] and [`StartedTestStep::end`] methods. /// After the scope is constructed, additional objects may be added to it. /// This is the preferred usage for the [`TestStep`], since it guarantees /// all the messages are emitted between the start and end messages, the order @@ -81,7 +80,6 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -112,6 +110,7 @@ impl TestStep { } } +/// TODO: docs pub struct StartedTestStep { step: TestStep, measurement_seqno: Arc, @@ -120,14 +119,13 @@ pub struct StartedTestStep { impl StartedTestStep { /// Ends the test step. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -145,17 +143,16 @@ impl StartedTestStep { } /// Emits Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. + /// This method accepts a [`tv::LogSeverity`] to define the severity + /// and a [`String`] for the message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -174,7 +171,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_log_info; /// /// let dut = DutInfo::new("my_dut"); @@ -203,22 +199,21 @@ impl StartedTestStep { } /// Emits Log message. - /// This method accepts a [`objects::Log`] object. + /// This method accepts a [`tv::Log`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_log_with_details( - /// &Log::builder("This is a log message with INFO severity") + /// step.add_log_detail( + /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), @@ -228,7 +223,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -238,16 +233,15 @@ impl StartedTestStep { } /// Emits an Error symptom. - /// This method accepts a [`std::string::String`] to define the symptom. + /// This method accepts a [`String`] to define the symptom. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -264,7 +258,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -289,22 +282,21 @@ impl StartedTestStep { } /// Emits an Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. + /// This method accepts a [`String`] to define the symptom and + /// another [`String`] as error message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_error_with_msg("symptom", "error message").await?; + /// step.add_error_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) @@ -316,7 +308,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -329,7 +320,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); self.step @@ -341,23 +332,22 @@ impl StartedTestStep { } /// Emits a Error message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_error_with_details( - /// &Error::builder("symptom") + /// step.add_error_detail( + /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&sw_info) @@ -368,7 +358,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -379,14 +369,13 @@ impl StartedTestStep { /// Emits an extension message; /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -415,14 +404,13 @@ impl StartedTestStep { /// Emits a Measurement message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -451,42 +439,41 @@ impl StartedTestStep { } /// Emits a Measurement message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) - /// .subcomponent(&Subcomponent::builder("name").build()) + /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); - /// step.add_measurement_with_details(&measurement).await?; + /// step.add_measurement_detail(measurement).await?; /// /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement_with_details( + pub async fn add_measurement_detail( &self, - measurement: &measure::Measurement, + detail: measure::Measurement, ) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&spec::TestStepArtifactImpl::Measurement( - measurement.to_artifact(), + detail.to_artifact(), )) .await?; @@ -494,17 +481,15 @@ impl StartedTestStep { } /// Create a Measurement Series (a time-series list of measurements). - /// This method accepts a [`std::string::String`] as series ID and - /// a [`std::string::String`] as series name. + /// This method accepts a [`String`] as series ID and a [`String`] as series name. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -514,37 +499,36 @@ impl StartedTestStep { /// # }); /// ``` pub fn add_measurement_series(&self, name: &str) -> tv::MeasurementSeries { - self.add_measurement_series_with_details(tv::MeasurementSeriesInfo::new(name)) + self.add_measurement_series_detail(tv::MeasurementSeriesDetail::new(name)) } /// Create a Measurement Series (a time-series list of measurements). - /// This method accepts a [`objects::MeasurementSeriesStart`] object. + /// This method accepts a [`tv::MeasurementSeriesDetail`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// let series = - /// step.add_measurement_series_with_details(MeasurementSeriesInfo::new("name")); + /// step.add_measurement_series_detail(MeasurementSeriesDetail::new("name")); /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub fn add_measurement_series_with_details( + pub fn add_measurement_series_detail( &self, - info: measure::MeasurementSeriesInfo, + detail: measure::MeasurementSeriesDetail, ) -> tv::MeasurementSeries { // spec says this identifier is unique in the scope of the test run, so create it from // the step identifier and a counter // ref: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/README.md#measurementseriesstart - let series_id = match &info.id { + let series_id = match &detail.id { Ident::Auto => format!( "{}_series{}", self.step.emitter.step_id, @@ -553,30 +537,29 @@ impl StartedTestStep { Ident::Exact(value) => value.to_owned(), }; - tv::MeasurementSeries::new(&series_id, info, Arc::clone(&self.step.emitter)) + tv::MeasurementSeries::new(&series_id, detail, Arc::clone(&self.step.emitter)) } /// Emits a Diagnosis message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.diagnosis("verdict", DiagnosisType::Pass).await?; + /// step.add_diagnosis("verdict", DiagnosisType::Pass).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn diagnosis( + pub async fn add_diagnosis( &self, verdict: &str, diagnosis_type: spec::DiagnosisType, @@ -592,16 +575,15 @@ impl StartedTestStep { } /// Emits a Diagnosis message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; @@ -613,16 +595,16 @@ impl StartedTestStep { /// .subcomponent(&Subcomponent::builder("name").build()) /// .source("file.rs", 1) /// .build(); - /// step.diagnosis_with_details(&diagnosis).await?; + /// step.add_diagnosis_detail(diagnosis).await?; /// /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn diagnosis_with_details( + pub async fn add_diagnosis_detail( &self, - diagnosis: &diagnosis::Diagnosis, + diagnosis: diagnosis::Diagnosis, ) -> Result<(), tv::OcptvError> { self.step .emitter @@ -636,26 +618,25 @@ impl StartedTestStep { /// Emits a File message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// let uri = Uri::parse("file:///tmp/foo").unwrap(); - /// step.file("name", uri).await?; + /// step.add_file("name", uri).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn file(&self, name: &str, uri: tv::Uri) -> Result<(), tv::OcptvError> { + pub async fn add_file(&self, name: &str, uri: tv::Uri) -> Result<(), tv::OcptvError> { let file = file::File::new(name, uri); self.step @@ -667,9 +648,9 @@ impl StartedTestStep { } /// Emits a File message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file + /// ref: /// /// # Examples /// @@ -677,7 +658,6 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// # use std::str::FromStr; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let uri = Uri::parse("file:///tmp/foo").unwrap(); @@ -690,13 +670,13 @@ impl StartedTestStep { /// .content_type(mime::TEXT_PLAIN) /// .add_metadata("key", "value".into()) /// .build(); - /// step.file_with_details(&file).await?; + /// step.add_file_detail(file).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn file_with_details(&self, file: &file::File) -> Result<(), tv::OcptvError> { + pub async fn add_file_detail(&self, file: file::File) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&spec::TestStepArtifactImpl::File(file.to_artifact())) diff --git a/src/output/trait_ext.rs b/src/output/trait_ext.rs index b3aae93..ce4e69e 100644 --- a/src/output/trait_ext.rs +++ b/src/output/trait_ext.rs @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::collections::BTreeMap; + pub trait VecExt { fn map_option(&self, func: F) -> Option> where @@ -18,3 +20,13 @@ impl VecExt for Vec { (!self.is_empty()).then_some(self.iter().map(func).collect()) } } + +pub trait MapExt { + fn option(&self) -> Option>; +} + +impl MapExt for BTreeMap { + fn option(&self) -> Option> { + (!self.is_empty()).then_some(self.clone()) + } +} diff --git a/src/output/writer.rs b/src/output/writer.rs index 8c2cca1..0905075 100644 --- a/src/output/writer.rs +++ b/src/output/writer.rs @@ -14,6 +14,7 @@ use tokio::fs; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; +/// TODO: docs #[async_trait] pub trait Writer { async fn write(&self, s: &str) -> Result<(), io::Error>; @@ -28,6 +29,7 @@ pub enum WriterType { Custom(Box), } +/// TODO: docs pub struct FileWriter { file: Arc>, } @@ -53,6 +55,7 @@ impl FileWriter { } } +/// TODO: docs #[derive(Debug)] pub struct BufferWriter { buffer: Arc>>, @@ -69,6 +72,7 @@ impl BufferWriter { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct StdoutWriter {} diff --git a/src/spec.rs b/src/spec.rs index a4bc678..a0cb26a 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -2,7 +2,7 @@ // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. +// use std::collections::BTreeMap; @@ -13,6 +13,7 @@ use serde_with::serde_as; use crate::output as tv; +/// TODO: docs pub const SPEC_VERSION: (i8, i8) = (2, 0); mod rfc3339_format { @@ -58,6 +59,7 @@ mod serialize_ids { } } +/// TODO: docs #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum ValidatorType { @@ -83,6 +85,7 @@ pub enum ValidatorType { NotInSet, } +/// TODO: docs #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum SubcomponentType { @@ -101,10 +104,14 @@ pub enum SubcomponentType { } /// Outcome of a diagnosis operation. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis/$defs/type +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone, Default)] +#[non_exhaustive] pub enum DiagnosisType { #[serde(rename = "PASS")] #[default] @@ -116,9 +123,12 @@ pub enum DiagnosisType { } /// Represents the final execution status of a test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststatus -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_status.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStatus +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testStatus")] #[non_exhaustive] @@ -132,9 +142,12 @@ pub enum TestStatus { } /// Represents the final outcome of a test execution. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testresult -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd/$defs/testResult +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testResult")] #[non_exhaustive] @@ -148,9 +161,12 @@ pub enum TestResult { } /// Known log severity variants. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#severity -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log/$defs/severity +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum LogSeverity { @@ -167,9 +183,12 @@ pub enum LogSeverity { } /// Type specification for a software component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwaretype -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo/properties/softwareType +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareType")] #[non_exhaustive] @@ -199,6 +218,7 @@ pub struct Root { } #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum RootImpl { #[serde(rename = "schemaVersion")] SchemaVersion(SchemaVersion), @@ -212,9 +232,12 @@ pub enum RootImpl { /// Low-level model for the `schemaVersion` spec object. /// Specifies the version that should be used to interpret following json outputs. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/root.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "schemaVersion")] pub struct SchemaVersion { @@ -236,9 +259,12 @@ impl Default for SchemaVersion { /// Low-level model for the `testRunArtifact` spec object. /// Container for the run level artifacts. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-run-artifacts -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunArtifact +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifact { #[serde(flatten)] @@ -246,6 +272,7 @@ pub struct TestRunArtifact { } #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum TestRunArtifactImpl { #[serde(rename = "testRunStart")] TestRunStart(TestRunStart), @@ -262,9 +289,12 @@ pub enum TestRunArtifactImpl { /// Low-level model for the `testRunStart` spec object. /// Start marker for the beginning of a diagnostic test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunStart")] pub struct TestRunStart { @@ -290,9 +320,12 @@ pub struct TestRunStart { /// Low-level model for the `dutInfo` spec object. /// Contains all relevant information describing the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#dutinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "dutInfo")] pub struct DutInfo { @@ -322,9 +355,12 @@ pub struct DutInfo { /// Low-level model for the `platformInfo` spec object. /// Describe platform specific attributes of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#platforminfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "platformInfo")] pub struct PlatformInfo { @@ -334,9 +370,12 @@ pub struct PlatformInfo { /// Low-level model for the `softwareInfo` spec object. /// Represents information of a discovered or exercised software component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwareinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareInfo")] pub struct SoftwareInfo { @@ -371,9 +410,12 @@ impl serialize_ids::IdGetter for SoftwareInfo { /// Low-level model for the `hardwareInfo` spec object. /// Represents information of an enumerated or exercised hardware component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#hardwareinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "hardwareInfo")] pub struct HardwareInfo { @@ -432,9 +474,12 @@ impl serialize_ids::IdGetter for HardwareInfo { /// Low-level model for the `testRunEnd` spec object. /// End marker signaling the finality of a diagnostic test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunEnd")] pub struct TestRunEnd { @@ -448,9 +493,12 @@ pub struct TestRunEnd { /// Low-level model for the `error` spec object. /// Represents an error encountered by the diagnostic software. It may refer to a DUT /// component or the diagnostic itself. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/error.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] @@ -474,9 +522,12 @@ pub struct Error { /// Low-level model for `log` spec object. /// Is currently relevant for test run and test step artifact types. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "log")] pub struct Log { @@ -493,9 +544,12 @@ pub struct Log { /// Provides information about which file/line of the source code in /// the diagnostic package generated the output. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#sourcelocation -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/source_location.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, Default, PartialEq)] #[serde(rename = "sourceLocation")] pub struct SourceLocation { @@ -508,9 +562,12 @@ pub struct SourceLocation { /// Low-level model for the `testStepArtifact` spec object. /// Container for the step level artifacts. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestStepArtifact { #[serde(rename = "testStepId")] @@ -522,6 +579,7 @@ pub struct TestStepArtifact { #[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum TestStepArtifactImpl { #[serde(rename = "testStepStart")] TestStepStart(TestStepStart), @@ -559,9 +617,12 @@ pub enum TestStepArtifactImpl { /// Low-level model for the `testStepStart` spec object. /// Start marker for a test step inside a diagnosis run. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepStart")] pub struct TestStepStart { @@ -571,9 +632,12 @@ pub struct TestStepStart { /// Low-level model for the `testStepEnd` spec object. /// End marker for a test step inside a diagnosis run. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepEnd")] pub struct TestStepEnd { @@ -583,9 +647,12 @@ pub struct TestStepEnd { /// Low-level model for the `measurement` spec object. /// Represents an individual measurement taken by the diagnostic regarding the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] @@ -620,9 +687,12 @@ pub struct Measurement { /// Low-level model for the `validator` spec object. /// Contains the validation logic that the diagnostic applied for a specific measurement. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#validator -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/validator.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] pub struct Validator { @@ -643,9 +713,12 @@ pub struct Validator { /// Low-level model for the `subcomponent` spec object. /// Represents a physical subcomponent of a DUT hardware element. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#subcomponent -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/subcomponent.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] pub struct Subcomponent { @@ -671,9 +744,12 @@ pub struct Subcomponent { /// Low-level model for the `measurementSeriesStart` spec object. /// Start marker for a time based series of measurements. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] @@ -708,9 +784,12 @@ pub struct MeasurementSeriesStart { /// Low-level model for the `measurementSeriesEnd` spec object. /// End marker for a time based series of measurements. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesEnd")] pub struct MeasurementSeriesEnd { @@ -723,9 +802,12 @@ pub struct MeasurementSeriesEnd { /// Low-level model for the `measurementSeriesElement` spec object. /// Equivalent to the `Measurement` model but inside a time based series. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_element.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesElement +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesElement")] pub struct MeasurementSeriesElement { @@ -748,9 +830,12 @@ pub struct MeasurementSeriesElement { /// Low-level model for the `diagnosis` spec object. /// Contains the verdict given by the diagnostic regarding the DUT that was inspected. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "diagnosis")] @@ -781,9 +866,12 @@ pub struct Diagnosis { /// Low-level model for the `file` spec object. /// Represents a file artifact that was generated by running the diagnostic. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/file.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] pub struct File { @@ -811,9 +899,12 @@ pub struct File { /// Low-level model for the `extension` spec object. /// Left as an implementation detail, the `Extension` just has a name and arbitrary data. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "extension")] pub struct Extension { diff --git a/tests/output/config.rs b/tests/output/config.rs new file mode 100644 index 0000000..81f0c57 --- /dev/null +++ b/tests/output/config.rs @@ -0,0 +1,88 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#[cfg(coverage)] +use anyhow::Result; + +// reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot +// disable the coverage itself, only run this test when in coverage mode because assert_fs +// does ultimately assume there's a real filesystem somewhere +#[cfg(coverage)] +#[tokio::test] +async fn test_config_builder_with_file() -> Result<()> { + use std::fs; + + use assert_fs::prelude::*; + use assert_json_diff::assert_json_include; + use predicates::prelude::*; + use serde_json::json; + + use ocptv::output::{Config, DutInfo, TestResult, TestRun, TestStatus}; + + use super::fixture::*; + + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "name": "run_name", + "parameters": {}, + "version": "1.0", + "commandLine": "" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + let fs = assert_fs::TempDir::new()?; + let output_file = fs.child("output.jsonl"); + + let dut = DutInfo::builder("dut_id").build(); + + let run = TestRun::builder("run_name", "1.0") + .config( + Config::builder() + .timezone(chrono_tz::Europe::Rome) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .with_file_output(output_file.path()) + .await? + .build(), + ) + .build() + .start(dut) + .await?; + + run.add_error_msg("symptom", "Error message").await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + + output_file.assert(predicate::path::exists()); + let content = fs::read_to_string(output_file.path())?; + + for (idx, entry) in content.lines().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} diff --git a/tests/output/diagnosis.rs b/tests/output/diagnosis.rs new file mode 100644 index 0000000..b2e7786 --- /dev/null +++ b/tests/output/diagnosis.rs @@ -0,0 +1,87 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{Diagnosis, DiagnosisType, Subcomponent}; + +use super::fixture::*; + +#[tokio::test] +async fn test_step_with_diagnosis() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "diagnosis": { + "verdict": "verdict", + "type": "PASS" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_diagnosis("verdict", DiagnosisType::Pass).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_diagnosis_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "diagnosis": { + "verdict": "verdict", + "type": "PASS", + "message": "message", + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) + .hardware_info(dut.hardware_info("hw0").unwrap()) // must exist + .subcomponent(&Subcomponent::builder("name").build()) + .message("message") + .build(); + s.add_diagnosis_detail(diagnosis).await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/error.rs b/tests/output/error.rs new file mode 100644 index 0000000..ce647d4 --- /dev/null +++ b/tests/output/error.rs @@ -0,0 +1,303 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::Error; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { r.add_error("symptom").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { r.add_error_msg("symptom", "Error message").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds": [ + "sw0" + ], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, dut| { + async move { + r.add_error_detail( + Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(dut.software_info("sw0").unwrap()) // must exist + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error("no-dut").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + "message": "failed to find dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_msg("no-dut", "failed to find dut").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "message": "failed to find dut", + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "no-dut" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_detail( + Error::builder("no-dut") + .message("failed to find dut") + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_error("symptom").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_error_msg("symptom", "Error message").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "message": "Error message", + "softwareInfoIds": [ + "sw0" + ], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + s.add_error_detail( + Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(dut.software_info("sw0").unwrap()) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/file.rs b/tests/output/file.rs new file mode 100644 index 0000000..d9d9065 --- /dev/null +++ b/tests/output/file.rs @@ -0,0 +1,91 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{File, Uri}; + +use super::fixture::*; + +#[tokio::test] +async fn test_step_with_file() -> Result<()> { + let uri = Uri::parse("file:///tmp/foo")?; + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "file": { + "name": "name", + "uri": uri.clone().as_str().to_owned(), + "isSnapshot": false + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_file("name", uri).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_file_builder() -> Result<()> { + let uri = Uri::parse("file:///tmp/foo")?; + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "file": { + "name": "name", + "uri": uri.clone().as_str().to_owned(), + "isSnapshot": false, + "contentType": "text/plain", + "description": "description", + "metadata": { + "key": "value" + }, + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + let file = File::builder("name", uri) + .content_type(mime::TEXT_PLAIN) + .description("description") + .add_metadata("key", "value".into()) + .build(); + s.add_file_detail(file).await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/fixture.rs b/tests/output/fixture.rs new file mode 100644 index 0000000..ff21d32 --- /dev/null +++ b/tests/output/fixture.rs @@ -0,0 +1,187 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use assert_json_diff::assert_json_eq; +use futures::future::BoxFuture; +use futures::future::Future; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{ + Config, DutInfo, HardwareInfo, Ident, OcptvError, SoftwareInfo, SoftwareType, StartedTestRun, + StartedTestStep, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, + SPEC_VERSION, +}; + +pub const DATETIME: chrono::DateTime = + chrono::DateTime::from_timestamp_nanos(0); +pub const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; +pub struct FixedTsProvider {} + +impl TimestampProvider for FixedTsProvider { + fn now(&self) -> chrono::DateTime { + // all cases will use time 0 but this is configurable + DATETIME.with_timezone(&chrono_tz::UTC) + } +} + +pub fn json_schema_version() -> serde_json::Value { + // seqno for schemaVersion is always 0 + json!({ + "schemaVersion": { + "major": SPEC_VERSION.0, + "minor": SPEC_VERSION.1 + }, + "sequenceNumber": 0, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_run_default_start() -> serde_json::Value { + // seqno for the default test run start is always 1 + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "name": "run_name", + "parameters": {}, + "version": "1.0", + "commandLine": "" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_run_pass(seqno: i32) -> serde_json::Value { + json!({ + "testRunArtifact": { + "testRunEnd": { + "result": "PASS", + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_step_default_start() -> serde_json::Value { + // seqno for the default test run start is always 2 + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepStart": { + "name": "first step" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_step_complete(seqno: i32) -> serde_json::Value { + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepEnd": { + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED + }) +} + +pub async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + R: Future>, + F: FnOnce(TestRunBuilder, DutInfo) -> R, +{ + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let mut dut = DutInfo::builder("dut_id").build(); + dut.add_software_info( + SoftwareInfo::builder("ubuntu") + .id(Ident::Exact("sw0".to_owned())) // name is important as fixture + .version("22") + .software_type(SoftwareType::System) + .build(), + ); + dut.add_hardware_info( + HardwareInfo::builder("fan") + .id(Ident::Exact("hw0".to_owned())) + .location("board0/fan") + .build(), + ); + + let run_builder = TestRun::builder("run_name", "1.0").config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .build(), + ); + + // run the main test closure + test_fn(run_builder, dut).await?; + + for (i, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_eq!(value, expected[i]); + } + + Ok(()) +} + +pub async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, +{ + check_output(expected, |run_builder, dut| async move { + let run = run_builder.build(); + + let run = run.start(dut.clone()).await?; + test_fn(&run, dut).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} + +pub async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, +{ + check_output(expected, |run_builder, dut| async move { + let run = run_builder.build().start(dut.clone()).await?; + + let step = run.add_step("first step").start().await?; + test_fn(&step, dut).await?; + step.end(TestStatus::Complete).await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} diff --git a/tests/output/log.rs b/tests/output/log.rs new file mode 100644 index 0000000..e3ae311 --- /dev/null +++ b/tests/output/log.rs @@ -0,0 +1,159 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{Log, LogSeverity}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_log_detail( + Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_log_detail( + Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/main.rs b/tests/output/main.rs index c07310d..dd6f89e 100644 --- a/tests/output/main.rs +++ b/tests/output/main.rs @@ -4,5 +4,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod config; +mod diagnosis; +mod error; +mod file; +mod fixture; +mod log; mod macros; -mod runner; +mod measure; +mod run; +mod step; diff --git a/tests/output/measure.rs b/tests/output/measure.rs new file mode 100644 index 0000000..61b037d --- /dev/null +++ b/tests/output/measure.rs @@ -0,0 +1,704 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{ + Ident, Measurement, MeasurementElementDetail, MeasurementSeriesDetail, Subcomponent, Validator, + ValidatorType, +}; + +use super::fixture::*; + +#[tokio::test] +async fn test_step_with_measurement() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurement": { + "name": "name", + "value": 50 + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_measurement("name", 50.into()).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurement": { + "name": "name", + "value": 50, + "validators": [{ + "type": "EQUAL", + "value": 30 + }], + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value", + "key2": "value2" + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + + let measurement = Measurement::builder("name", 50.into()) + .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .hardware_info(hw_info) + .subcomponent(Subcomponent::builder("name").build()) + .build(); + s.add_measurement_detail(measurement).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_multiple_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series1", + "name": "name" + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series1", + "totalCount": 0 + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(7), + json_run_pass(8), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; + + let series_2 = s.add_measurement_series("name").start().await?; + series_2.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "series_id", + "name": "name", + "unit": "unit", + "validators": [{ + "type": "EQUAL", + "value": 30 + }], + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value", + "key2": "value2" + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + + let series = s + .add_measurement_series_detail( + MeasurementSeriesDetail::builder("name") + .id(Ident::Exact("series_id".to_owned())) + .unit("unit") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) + .hardware_info(hw_info) + .subcomponent(Subcomponent::builder("name").build()) + .build(), + ) + .start() + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 1 + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.add_measurement(60.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "value": 70, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "value": 80, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + // add more than one element to check the index increments correctly + series.add_measurement(60.into()).await?; + series.add_measurement(70.into()).await?; + series.add_measurement(80.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "metadata": { + "key": "value", + "key2": "value2" + }, + "value": 60, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 1 + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series + .add_measurement_detail( + MeasurementElementDetail::builder(60.into()) + .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .build(), + ) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "metadata": {"key": "value"}, + "value": 60, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "metadata": {"key2": "value2"}, + "value": 70, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "metadata": {"key3": "value3"}, + "value": 80, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + // add more than one element to check the index increments correctly + series + .add_measurement_detail( + MeasurementElementDetail::builder(60.into()) + .add_metadata("key", "value".into()) + .build(), + ) + .await?; + series + .add_measurement_detail( + MeasurementElementDetail::builder(70.into()) + .add_metadata("key2", "value2".into()) + .build(), + ) + .await?; + series + .add_measurement_detail( + MeasurementElementDetail::builder(80.into()) + .add_metadata("key3", "value3".into()) + .build(), + ) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_step_with_measurement_series_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "value": 70, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "value": 80, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name"); + series + .scope(|s| { + async move { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; + + Ok(()) + } + .boxed() + }) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/run.rs b/tests/output/run.rs new file mode 100644 index 0000000..1da27f3 --- /dev/null +++ b/tests/output/run.rs @@ -0,0 +1,194 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use assert_json_diff::assert_json_include; +use futures::FutureExt; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{DutInfo, TestResult, TestRun, TestStatus}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_start_and_end() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(2), + ]; + + check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_testrun_with_scope() -> Result<()> { + use ocptv::output::{LogSeverity, TestResult, TestRunOutcome, TestStatus}; + + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "First message", + "severity": "INFO" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder.build(); + + run.scope(dut, |r| { + async move { + r.add_log(LogSeverity::Info, "First message").await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_instantiation_with_new() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(2), + ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::new("run_name", "1.0").start(dut).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} + +#[tokio::test] +async fn test_testrun_metadata() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "metadata": {"key": "value"}, + "name": "run_name", + "parameters": {}, + "version": "1.0", + + "commandLine": "", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(2), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder + .add_metadata("key", "value".into()) + .build() + .start(dut) + .await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "commandLine": "cmd_line", + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "metadata": { + "key": "value", + "key2": "value2" + }, + "name": "run_name", + "parameters": { + "key": "value" + }, + "version": "1.0" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(2), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_parameter("key", "value".into()) + .command_line("cmd_line") + .build() + .start(dut) + .await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} diff --git a/tests/output/runner.rs b/tests/output/runner.rs deleted file mode 100644 index 4264b4b..0000000 --- a/tests/output/runner.rs +++ /dev/null @@ -1,1878 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use std::sync::Arc; - -use anyhow::Result; - -use assert_json_diff::{assert_json_eq, assert_json_include}; -use futures::future::BoxFuture; -use futures::future::Future; -use futures::FutureExt; -use serde_json::json; -use tokio::sync::Mutex; - -use ocptv::output as tv; -use ocptv::output::OcptvError; -#[cfg(feature = "boxed-scopes")] -use tv::TestRunOutcome; -use tv::{ - Config, Diagnosis, DutInfo, Error, File, HardwareInfo, Ident, Log, LogSeverity, Measurement, - MeasurementSeriesElemDetails, MeasurementSeriesInfo, SoftwareInfo, SoftwareType, - StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, TestRunBuilder, TestStatus, - TimestampProvider, Validator, ValidatorType, -}; - -const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); -const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; -struct FixedTsProvider {} - -impl TimestampProvider for FixedTsProvider { - fn now(&self) -> chrono::DateTime { - // all cases will use time 0 but this is configurable - DATETIME.with_timezone(&chrono_tz::UTC) - } -} - -fn json_schema_version() -> serde_json::Value { - // seqno for schemaVersion is always 0 - json!({ - "schemaVersion": { - "major": tv::SPEC_VERSION.0, - "minor": tv::SPEC_VERSION.1 - }, - "sequenceNumber": 0, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_run_default_start() -> serde_json::Value { - // seqno for the default test run start is always 1 - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "name": "run_name", - "parameters": {}, - "version": "1.0", - "commandLine": "" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_run_pass(seqno: i32) -> serde_json::Value { - json!({ - "testRunArtifact": { - "testRunEnd": { - "result": "PASS", - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_step_default_start() -> serde_json::Value { - // seqno for the default test run start is always 2 - json!({ - "testStepArtifact": { - "testStepId": "step0", - "testStepStart": { - "name": "first step" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_step_complete(seqno: i32) -> serde_json::Value { - json!({ - "testStepArtifact": { - "testStepId": "step0", - "testStepEnd": { - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno, - "timestamp": DATETIME_FORMATTED - }) -} - -async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - R: Future>, - F: FnOnce(TestRunBuilder, DutInfo) -> R, -{ - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let mut dut = DutInfo::builder("dut_id").build(); - dut.add_software_info( - SoftwareInfo::builder("ubuntu") - .id(Ident::Exact("sw0".to_owned())) // name is important as fixture - .version("22") - .software_type(SoftwareType::System) - .build(), - ); - dut.add_hardware_info( - HardwareInfo::builder("fan") - .id(Ident::Exact("hw0".to_owned())) - .location("board0/fan") - .build(), - ); - - let run_builder = TestRun::builder("run_name", "1.0").config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .build(), - ); - - // run the main test closure - test_fn(run_builder, dut).await?; - - for (i, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_eq!(value, expected[i]); - } - - Ok(()) -} - -async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, -{ - check_output(expected, |run_builder, dut| async move { - let run = run_builder.build(); - - let run = run.start(dut.clone()).await?; - test_fn(&run, dut).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await -} - -async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, -{ - check_output(expected, |run_builder, dut| async move { - let run = run_builder.build().start(dut.clone()).await?; - - let step = run.add_step("first step").start().await?; - test_fn(&step, dut).await?; - step.end(TestStatus::Complete).await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_start_and_end() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_run_pass(2), - ]; - - check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await -} - -#[tokio::test] -async fn test_testrun_with_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { r.add_error("symptom").await }.boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { r.add_error_with_msg("symptom", "Error message").await }.boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "softwareInfoIds": [ - "sw0" - ], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, dut| { - async move { - r.add_error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(dut.software_info("sw0").unwrap()) // must exist - .build(), - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "no-dut", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error("no-dut").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_message_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "no-dut", - "message": "failed to find dut", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_with_msg("no-dut", "failed to find dut") - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_details_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "message": "failed to find dut", - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "no-dut" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_with_details( - &Error::builder("no-dut") - .message("failed to find dut") - .source("file", 1) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_testrun_with_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "First message", - "severity": "INFO" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder.build(); - - run.scope(dut, |r| { - async move { - r.add_log(LogSeverity::Info, "First message").await?; - - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() - }) - .await?; - - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_step() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json_step_complete(3), - json_run_pass(4), - ]; - - check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await -} - -#[tokio::test] -async fn test_testrun_step_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_error("symptom").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_error_with_msg("symptom", "Error message").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "message": "Error message", - "softwareInfoIds": [ - "sw0" - ], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - s.add_error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(dut.software_info("sw0").unwrap()) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_testrun_step_scope_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_step("first step") - .scope(|s| { - async move { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(TestStatus::Complete) - } - .boxed() - }) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurement": { - "name": "name", - "value": 50 - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_measurement("name", 50.into()).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurement": { - "name": "name", - "value": 50, - "validators": [{ - "type": "EQUAL", - "value": 30 - }], - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - "metadata": { - "key": "value", - "key2": "value2" - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let hw_info = dut.hardware_info("hw0").unwrap(); // must exist - - let measurement = Measurement::builder("name", 50.into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) - .build(); - s.add_measurement_with_details(&measurement).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_multiple_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series1", - "name": "name" - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series1", - "totalCount": 0 - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(7), - json_run_pass(8), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; - - let series_2 = s.add_measurement_series("name").start().await?; - series_2.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "series_id", - "name": "name", - "unit": "unit", - "validators": [{ - "type": "EQUAL", - "value": 30 - }], - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - "metadata": { - "key": "value", - "key2": "value2" - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "series_id", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let hw_info = dut.hardware_info("hw0").unwrap(); // must exist - - let series = s - .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("name") - .id(Ident::Exact("series_id".to_owned())) - .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) - .build(), - ) - .start() - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 1 - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.add_measurement(60.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "value": 70, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "value": 80, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - // add more than one element to check the index increments correctly - series.add_measurement(60.into()).await?; - series.add_measurement(70.into()).await?; - series.add_measurement(80.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "metadata": { - "key": "value", - "key2": "value2" - }, - "value": 60, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 1 - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) - .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .build(), - ) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "metadata": {"key": "value"}, - "value": 60, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "metadata": {"key2": "value2"}, - "value": 70, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "metadata": {"key3": "value3"}, - "value": 80, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - // add more than one element to check the index increments correctly - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) - .add_metadata("key", "value".into()) - .build(), - ) - .await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(70.into()) - .add_metadata("key2", "value2".into()) - .build(), - ) - .await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(80.into()) - .add_metadata("key3", "value3".into()) - .build(), - ) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_diagnosis() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "diagnosis": { - "verdict": "verdict", - "type": "PASS" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.diagnosis("verdict", tv::DiagnosisType::Pass).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_diagnosis_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "diagnosis": { - "verdict": "verdict", - "type": "PASS", - "message": "message", - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let diagnosis = Diagnosis::builder("verdict", tv::DiagnosisType::Pass) - .hardware_info(dut.hardware_info("hw0").unwrap()) // must exist - .subcomponent(&Subcomponent::builder("name").build()) - .message("message") - .build(); - s.diagnosis_with_details(&diagnosis).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_file() -> Result<()> { - let uri = tv::Uri::parse("file:///tmp/foo")?; - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "file": { - "name": "name", - "uri": uri.clone().as_str().to_owned(), - "isSnapshot": false - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.file("name", uri).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_file_builder() -> Result<()> { - let uri = tv::Uri::parse("file:///tmp/foo")?; - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "file": { - "name": "name", - "uri": uri.clone().as_str().to_owned(), - "isSnapshot": false, - "contentType": "text/plain", - "description": "description", - "metadata": { - "key": "value" - }, - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - let file = File::builder("name", uri) - .content_type(mime::TEXT_PLAIN) - .description("description") - .add_metadata("key", "value".into()) - .build(); - s.file_with_details(&file).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_step_with_measurement_series_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "value": 70, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "value": 80, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name"); - series - .scope(|s| { - async move { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; - - Ok(()) - } - .boxed() - }) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -// reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot -// disable the coverage itself, only run this test when in coverage mode because assert_fs -// does ultimately assume there's a real filesystem somewhere -#[cfg(coverage)] -#[tokio::test] -async fn test_config_builder_with_file() -> Result<()> { - use assert_fs::prelude::*; - use predicates::prelude::*; - use std::fs; - - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id" - }, - "name": "run_name", - "parameters": {}, - "version": "1.0", - "commandLine": "" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - let fs = assert_fs::TempDir::new()?; - let output_file = fs.child("output.jsonl"); - - let dut = DutInfo::builder("dut_id").build(); - - let run = TestRun::builder("run_name", "1.0") - .config( - Config::builder() - .timezone(chrono_tz::Europe::Rome) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .with_file_output(output_file.path()) - .await? - .build(), - ) - .build() - .start(dut) - .await?; - - run.add_error_with_msg("symptom", "Error message").await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - - output_file.assert(predicate::path::exists()); - let content = fs::read_to_string(output_file.path())?; - - for (idx, entry) in content.lines().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) -} - -#[tokio::test] -async fn test_step_with_extension() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "extension": { - "name": "extension", - "content": { - "@type": "TestExtension", - "stringField": "string", - "numberField": 42 - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - #[derive(serde::Serialize)] - struct Ext { - #[serde(rename = "@type")] - r#type: String, - #[serde(rename = "stringField")] - string_field: String, - #[serde(rename = "numberField")] - number_field: u32, - } - - check_output_step(&expected, |s, _| { - async { - s.add_extension( - "extension", - Ext { - r#type: "TestExtension".to_owned(), - string_field: "string".to_owned(), - number_field: 42, - }, - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_extension_which_fails() -> Result<()> { - #[derive(thiserror::Error, Debug, PartialEq)] - enum TestError { - #[error("test_error_fail")] - Fail, - } - - fn fail_serialize(_: &u32, _serializer: S) -> Result - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom(TestError::Fail)) - } - - #[derive(serde::Serialize)] - struct Ext { - #[serde(serialize_with = "fail_serialize")] - i: u32, - } - - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .build(), - ) - .build() - .start(dut) - .await?; - let step = run.add_step("first step").start().await?; - - let result = step.add_extension("extension", Ext { i: 0 }).await; - - match result { - Err(OcptvError::Format(e)) => { - // `to_string` is the only way to check this error. `serde_json::Error` only - // implements source/cause for io errors, and this is a string - assert_eq!(e.to_string(), "test_error_fail"); - } - _ => panic!("unexpected ocptv error type"), - } - - Ok(()) -} - -#[tokio::test] -async fn test_testrun_instantiation_with_new() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_run_pass(2), - ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::new("run_name", "1.0").start(dut).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) -} - -#[tokio::test] -async fn test_testrun_metadata() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "metadata": {"key": "value"}, - "name": "run_name", - "parameters": {}, - "version": "1.0", - - "commandLine": "", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(2), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder - .add_metadata("key", "value".into()) - .build() - .start(dut) - .await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "commandLine": "cmd_line", - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "metadata": { - "key": "value", - "key2": "value2" - }, - "name": "run_name", - "parameters": { - "key": "value" - }, - "version": "1.0" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(2), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_parameter("key", "value".into()) - .command_line("cmd_line") - .build() - .start(dut) - .await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await -} diff --git a/tests/output/step.rs b/tests/output/step.rs new file mode 100644 index 0000000..b74026d --- /dev/null +++ b/tests/output/step.rs @@ -0,0 +1,178 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{Config, DutInfo, OcptvError, TestRun}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_step() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json_step_complete(3), + json_run_pass(4), + ]; + + check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_testrun_step_scope_log() -> Result<()> { + use ocptv::output::{LogSeverity, TestStatus}; + + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_step("first step") + .scope(|s| { + async move { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(TestStatus::Complete) + } + .boxed() + }) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_extension() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "extension": { + "name": "extension", + "content": { + "@type": "TestExtension", + "stringField": "string", + "numberField": 42 + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + #[derive(serde::Serialize)] + struct Ext { + #[serde(rename = "@type")] + r#type: String, + #[serde(rename = "stringField")] + string_field: String, + #[serde(rename = "numberField")] + number_field: u32, + } + + check_output_step(&expected, |s, _| { + async { + s.add_extension( + "extension", + Ext { + r#type: "TestExtension".to_owned(), + string_field: "string".to_owned(), + number_field: 42, + }, + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_extension_which_fails() -> Result<()> { + #[derive(thiserror::Error, Debug, PartialEq)] + enum TestError { + #[error("test_error_fail")] + Fail, + } + + fn fail_serialize(_: &u32, _serializer: S) -> Result + where + S: serde::Serializer, + { + Err(serde::ser::Error::custom(TestError::Fail)) + } + + #[derive(serde::Serialize)] + struct Ext { + #[serde(serialize_with = "fail_serialize")] + i: u32, + } + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::builder("run_name", "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .build(), + ) + .build() + .start(dut) + .await?; + let step = run.add_step("first step").start().await?; + + let result = step.add_extension("extension", Ext { i: 0 }).await; + + match result { + Err(OcptvError::Format(e)) => { + // `to_string` is the only way to check this error. `serde_json::Error` only + // implements source/cause for io errors, and this is a string + assert_eq!(e.to_string(), "test_error_fail"); + } + _ => panic!("unexpected ocptv error type"), + } + + Ok(()) +}