From 3381b2ff148c226315c22d1fd86dca0cb8874405 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 18:07:39 +0100 Subject: [PATCH 1/5] split runner.rs into smaller files - this refactoring is in preparation for the next steps (commits on this branch) - smaller files makes the scope of the objects easier to reason about, grouping them by ocptv topic Signed-off-by: mimir-d --- src/output/config.rs | 77 ++ src/output/measurement_series.rs | 241 ++++++ src/output/mod.rs | 10 +- src/output/run.rs | 493 ++++++++++++ src/output/runner.rs | 1291 ------------------------------ src/output/state.rs | 18 + src/output/step.rs | 507 ++++++++++++ 7 files changed, 1344 insertions(+), 1293 deletions(-) create mode 100644 src/output/config.rs create mode 100644 src/output/measurement_series.rs create mode 100644 src/output/run.rs delete mode 100644 src/output/runner.rs create mode 100644 src/output/state.rs create mode 100644 src/output/step.rs diff --git a/src/output/config.rs b/src/output/config.rs new file mode 100644 index 0000000..f97baaa --- /dev/null +++ b/src/output/config.rs @@ -0,0 +1,77 @@ +// (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::path::Path; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::output::emitters; + +/// The configuration repository for the TestRun. +pub struct Config { + pub(crate) timezone: chrono_tz::Tz, + pub(crate) writer: emitters::WriterType, +} + +impl Config { + /// Creates a new [`ConfigBuilder`] + /// + /// # Examples + /// ```rust + /// # use ocptv::output::*; + /// + /// let builder = Config::builder(); + /// ``` + pub fn builder() -> ConfigBuilder { + ConfigBuilder::new() + } +} + +/// The builder for the [`Config`] object. +pub struct ConfigBuilder { + timezone: Option, + writer: Option, +} + +impl ConfigBuilder { + fn new() -> Self { + Self { + timezone: None, + writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } + + pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { + self.timezone = Some(timezone); + self + } + + pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { + self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( + buffer, + ))); + self + } + + pub async fn with_file_output>( + mut self, + path: P, + ) -> Result { + self.writer = Some(emitters::WriterType::File( + emitters::FileWriter::new(path).await?, + )); + Ok(self) + } + + pub fn build(self) -> Config { + Config { + timezone: self.timezone.unwrap_or(chrono_tz::UTC), + writer: self + .writer + .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } +} diff --git a/src/output/measurement_series.rs b/src/output/measurement_series.rs new file mode 100644 index 0000000..c7372c2 --- /dev/null +++ b/src/output/measurement_series.rs @@ -0,0 +1,241 @@ +// (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::future::Future; +use std::sync::atomic; +use std::sync::Arc; + +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::{emitters, objects, state}; + +/// 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 +pub struct MeasurementSeries { + state: Arc>, + seq_no: Arc>, + start: objects::MeasurementSeriesStart, +} + +impl MeasurementSeries { + pub fn new(series_id: &str, name: &str, state: Arc>) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start: objects::MeasurementSeriesStart::new(name, series_id), + } + } + + pub fn new_with_details( + start: objects::MeasurementSeriesStart, + state: Arc>, + ) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start, + } + } + + async fn current_sequence_no(&self) -> u64 { + self.seq_no.lock().await.load(atomic::Ordering::SeqCst) + } + + async fn increment_sequence_no(&self) { + self.seq_no + .lock() + .await + .fetch_add(1, atomic::Ordering::SeqCst); + } + + /// Starts the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&self.start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.end().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self) -> Result<(), emitters::WriterError> { + let end = objects::MeasurementSeriesEnd::new( + self.start.get_series_id(), + self.current_sequence_no().await, + ); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement(60.into()).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + None, + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// This method accepts additional metadata to add to the element. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_metadata( + &self, + value: Value, + metadata: Vec<(&str, Value)>, + ) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + Some(Map::from_iter( + metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), + )), + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::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 + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.scope(|s| async { + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; + /// Ok(()) + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + { + self.start().await?; + func(self).await?; + self.end().await?; + Ok(()) + } +} diff --git a/src/output/mod.rs b/src/output/mod.rs index 231567f..c0d0a6e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -4,12 +4,17 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod config; mod emitters; mod macros; +mod measurement_series; mod models; mod objects; -mod runner; +mod run; +mod state; +mod step; +pub use config::*; pub use emitters::*; pub use models::LogSeverity; pub use models::TestResult; @@ -17,5 +22,6 @@ pub use models::TestStatus; pub use models::ValidatorType; pub use models::SPEC_VERSION; pub use objects::*; -pub use runner::*; +pub use run::*; pub use serde_json::Value; +pub use step::*; diff --git a/src/output/run.rs b/src/output/run.rs new file mode 100644 index 0000000..b3d4a9f --- /dev/null +++ b/src/output/run.rs @@ -0,0 +1,493 @@ +// (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::env; +use std::sync::Arc; + +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::config; +use tv::emitters; +use tv::models; +use tv::objects; +use tv::state; +use tv::step::TestStep; + +/// The outcome of a TestRun. +/// It's returned when the scope method of the [`TestRun`] object is used. +pub struct TestRunOutcome { + /// Reports the execution status of the test + pub status: models::TestStatus, + /// Reports the result of the test + pub result: models::TestResult, +} + +/// The main diag test run. +/// +/// This object describes a single run instance of the diag, and therefore drives the test session. +pub struct TestRun { + name: String, + version: String, + parameters: Map, + dut: objects::DutInfo, + command_line: String, + metadata: Option>, + state: Arc>, +} + +impl TestRun { + /// Creates a new [`TestRunBuilder`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("my_dut").build(); + /// let builder = TestRun::builder("run_name", &dut, "1.0"); + /// ``` + pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { + TestRunBuilder::new(name, dut, version) + } + + /// Creates a new [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// ``` + pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { + let dut = objects::DutInfo::new(dut_id); + TestRunBuilder::new(name, &dut, version).build() + } + + /// 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 + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// run.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(self) -> Result { + let version = objects::SchemaVersion::new(); + self.state + .lock() + .await + .emitter + .emit(&version.to_artifact()) + .await?; + + let mut builder = objects::TestRunStart::builder( + &self.name, + &self.version, + &self.command_line, + &self.parameters, + &self.dut, + ); + + if let Some(m) = &self.metadata { + for m in m { + builder = builder.add_metadata(m.0, m.1.clone()) + } + } + + let start = builder.build(); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + + Ok(StartedTestRun { run: self }) + } + + // disabling this for the moment so we don't publish api that's unusable. + // see: https://github.com/rust-lang/rust/issues/70263 + // + // /// Builds a scope in the [`TestRun`] object, taking care of starting and + // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`TestRun`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + // /// run.scope(|r| async { + // /// r.log(LogSeverity::Info, "First message").await?; + // /// Ok(TestRunOutcome { + // /// status: TestStatus::Complete, + // /// result: TestResult::Pass, + // /// }) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // for<'a> F: Fut2<'a, R>, + // { + // let run = self.start().await?; + // let outcome = func(&run).await?; + // run.end(outcome.status, outcome.result).await?; + + // Ok(()) + // } +} + +/// Builder for the [`TestRun`] object. +pub struct TestRunBuilder { + name: String, + dut: objects::DutInfo, + version: String, + parameters: Map, + command_line: String, + metadata: Option>, + config: Option, +} + +impl TestRunBuilder { + pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + Self { + name: name.to_string(), + dut: dut.clone(), + version: version.to_string(), + parameters: Map::new(), + command_line: env::args().collect::>()[1..].join(" "), + metadata: None, + config: None, + } + } + + /// Adds a user defined parameter to the future [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_parameter("param1", "value1".into()) + /// .build(); + /// ``` + pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { + self.parameters.insert(key.to_string(), value.clone()); + self + } + + /// Adds the command line used to run the test session to the future + /// [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .command_line("my_diag --arg value") + /// .build(); + /// ``` + pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + self.command_line = cmd.to_string(); + self + } + + /// Adds the configuration for the test session to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .config(Config::builder().build()) + /// .build(); + /// ``` + pub fn config(mut self, value: config::Config) -> TestRunBuilder { + self.config = Some(value); + self + } + + /// Adds user defined metadata to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_metadata("meta1", "value1".into()) + /// .build(); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRun { + let config = self.config.unwrap_or(config::Config::builder().build()); + let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let state = state::TestState::new(emitter); + TestRun { + name: self.name, + dut: self.dut, + version: self.version, + parameters: self.parameters, + command_line: self.command_line, + metadata: self.metadata, + state: Arc::new(Mutex::new(state)), + } + } +} + +/// A test run that was started. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +pub struct StartedTestRun { + run: TestRun, +} + +impl StartedTestRun { + /// Ends the test run. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end( + &self, + status: models::TestStatus, + result: models::TestResult, + ) -> Result<(), emitters::WriterError> { + let end = objects::TestRunEnd::builder() + .status(status) + .result(result) + .build(); + + let emitter = &self.run.state.lock().await.emitter; + + emitter.emit(&end.to_artifact()).await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error("symptom").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .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. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_msg("symptom", "error messasge").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method acceps a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + pub fn step(&self, name: &str) -> TestStep { + TestStep::new(name, self.run.state.clone()) + } +} diff --git a/src/output/runner.rs b/src/output/runner.rs deleted file mode 100644 index 681e565..0000000 --- a/src/output/runner.rs +++ /dev/null @@ -1,1291 +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. - -//! OCPTV library runner -//! -//! This module contains the main entry point for the test runner. This is the -//! main object the user will interact with. - -use std::env; -use std::future::Future; -use std::path::Path; -use std::sync::atomic; -use std::sync::Arc; - -use serde_json::Map; -use serde_json::Value; -use tokio::sync::Mutex; - -use crate::output::emitters; -use crate::output::models; -use crate::output::objects; - -/// The configuration repository for the TestRun. -pub struct Config { - timezone: chrono_tz::Tz, - writer: emitters::WriterType, -} - -impl Config { - /// Creates a new [`ConfigBuilder`] - /// - /// # Examples - /// ```rust - /// # use ocptv::output::*; - /// - /// let builder = Config::builder(); - /// ``` - pub fn builder() -> ConfigBuilder { - ConfigBuilder::new() - } -} - -/// The builder for the [`Config`] object. -pub struct ConfigBuilder { - timezone: Option, - writer: Option, -} - -impl ConfigBuilder { - fn new() -> Self { - Self { - timezone: None, - writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), - } - } - - pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { - self.timezone = Some(timezone); - self - } - - pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { - self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( - buffer, - ))); - self - } - - pub async fn with_file_output>( - mut self, - path: P, - ) -> Result { - self.writer = Some(emitters::WriterType::File( - emitters::FileWriter::new(path).await?, - )); - Ok(self) - } - - pub fn build(self) -> Config { - Config { - timezone: self.timezone.unwrap_or(chrono_tz::UTC), - writer: self - .writer - .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), - } - } -} - -/// The outcome of a TestRun. -/// It's returned when the scope method of the [`TestRun`] object is used. -pub struct TestRunOutcome { - /// Reports the execution status of the test - pub status: models::TestStatus, - /// Reports the result of the test - pub result: models::TestResult, -} - -struct TestState { - emitter: emitters::JsonEmitter, -} - -impl TestState { - fn new(emitter: emitters::JsonEmitter) -> TestState { - TestState { emitter } - } -} - -/// The main diag test run. -/// -/// This object describes a single run instance of the diag, and therefore drives the test session. -pub struct TestRun { - name: String, - version: String, - parameters: Map, - dut: objects::DutInfo, - command_line: String, - metadata: Option>, - state: Arc>, -} - -impl TestRun { - /// Creates a new [`TestRunBuilder`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("my_dut").build(); - /// let builder = TestRun::builder("run_name", &dut, "1.0"); - /// ``` - pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { - TestRunBuilder::new(name, dut, version) - } - - /// Creates a new [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// ``` - pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { - let dut = objects::DutInfo::new(dut_id); - TestRunBuilder::new(name, &dut, version).build() - } - - /// 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 - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// run.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(self) -> Result { - let version = objects::SchemaVersion::new(); - self.state - .lock() - .await - .emitter - .emit(&version.to_artifact()) - .await?; - - let mut builder = objects::TestRunStart::builder( - &self.name, - &self.version, - &self.command_line, - &self.parameters, - &self.dut, - ); - - if let Some(m) = &self.metadata { - for m in m { - builder = builder.add_metadata(m.0, m.1.clone()) - } - } - - let start = builder.build(); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; - - Ok(StartedTestRun { run: self }) - } - - // disabling this for the moment so we don't publish api that's unusable. - // see: https://github.com/rust-lang/rust/issues/70263 - // - // /// Builds a scope in the [`TestRun`] object, taking care of starting and - // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`TestRun`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - // /// run.scope(|r| async { - // /// r.log(LogSeverity::Info, "First message").await?; - // /// Ok(TestRunOutcome { - // /// status: TestStatus::Complete, - // /// result: TestResult::Pass, - // /// }) - // /// }).await?; - // /// - // /// # Ok::<(), WriterError>(()) - // /// # }); - // /// ``` - // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // for<'a> F: Fut2<'a, R>, - // { - // let run = self.start().await?; - // let outcome = func(&run).await?; - // run.end(outcome.status, outcome.result).await?; - - // Ok(()) - // } -} - -/// Builder for the [`TestRun`] object. -pub struct TestRunBuilder { - name: String, - dut: objects::DutInfo, - version: String, - parameters: Map, - command_line: String, - metadata: Option>, - config: Option, -} - -impl TestRunBuilder { - pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { - Self { - name: name.to_string(), - dut: dut.clone(), - version: version.to_string(), - parameters: Map::new(), - command_line: env::args().collect::>()[1..].join(" "), - metadata: None, - config: None, - } - } - - /// Adds a user defined parameter to the future [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_parameter("param1", "value1".into()) - /// .build(); - /// ``` - pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { - self.parameters.insert(key.to_string(), value.clone()); - self - } - - /// Adds the command line used to run the test session to the future - /// [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .command_line("my_diag --arg value") - /// .build(); - /// ``` - pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { - self.command_line = cmd.to_string(); - self - } - - /// Adds the configuration for the test session to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .config(Config::builder().build()) - /// .build(); - /// ``` - pub fn config(mut self, value: Config) -> TestRunBuilder { - self.config = Some(value); - self - } - - /// Adds user defined metadata to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_metadata("meta1", "value1".into()) - /// .build(); - /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRun { - let config = self.config.unwrap_or(Config::builder().build()); - let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); - let state = TestState::new(emitter); - TestRun { - name: self.name, - dut: self.dut, - version: self.version, - parameters: self.parameters, - command_line: self.command_line, - metadata: self.metadata, - state: Arc::new(Mutex::new(state)), - } - } -} - -/// A test run that was started. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart -pub struct StartedTestRun { - run: TestRun, -} - -impl StartedTestRun { - /// Ends the test run. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end( - &self, - status: models::TestStatus, - result: models::TestResult, - ) -> Result<(), emitters::WriterError> { - let end = objects::TestRunEnd::builder() - .status(status) - .result(result) - .build(); - - let emitter = &self.run.state.lock().await.emitter; - - emitter.emit(&end.to_artifact()).await?; - Ok(()) - } - - /// Emits a Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log( - /// LogSeverity::Info, - /// "This is a log message with INFO severity", - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log( - &self, - severity: models::LogSeverity, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); - - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Log message. - /// This method accepts a [`objects::Log`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log_with_details( - /// &Log::builder("This is a log message with INFO severity") - /// .severity(LogSeverity::Info) - /// .source("file", 1) - /// .build(), - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error("symptom").await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .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. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_msg("symptom", "error messasge").await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method acceps a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_details( - /// &Error::builder("symptom") - /// .message("Error message") - /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) - /// .build(), - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_details( - &self, - error: &objects::Error, - ) -> Result<(), emitters::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - pub fn step(&self, name: &str) -> TestStep { - TestStep::new(name, self.run.state.clone()) - } -} - -/// A single test step in the scope of a [`TestRun`]. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts -pub struct TestStep { - name: String, - state: Arc>, -} - -impl TestStep { - fn new(name: &str, state: Arc>) -> TestStep { - TestStep { - name: name.to_string(), - state, - } - } - - /// Starts the test step. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(self) -> Result { - let start = objects::TestStepStart::new(&self.name); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; - - Ok(StartedTestStep { - step: self, - measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), - }) - } - - // /// Builds a scope in the [`TestStep`] object, taking care of starting and - // /// ending it. View [`TestStep::start`] and [`TestStep::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 - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - // /// - // /// let step = run.step("first step")?; - // /// step.scope(|s| async { - // /// s.log( - // /// LogSeverity::Info, - // /// "This is a log message with INFO severity", - // /// ).await?; - // /// Ok(TestStatus::Complete) - // /// }).await?; - // /// - // /// # Ok::<(), WriterError>(()) - // /// # }); - // /// ``` - // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // F: std::ops::FnOnce(&'a TestStep) -> R, - // { - // self.start().await?; - // let status = func(self).await?; - // self.end(status).await?; - // Ok(()) - // } -} - -pub struct StartedTestStep { - step: TestStep, - measurement_id_no: Arc, -} - -impl StartedTestStep { - /// Ends the test step. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { - let end = objects::TestStepEnd::new(status); - self.step - .state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Eemits Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.log( - /// LogSeverity::Info, - /// "This is a log message with INFO severity", - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_log_info; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log( - &self, - severity: models::LogSeverity, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits Log message. - /// This method accepts a [`objects::Log`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.log_with_details( - /// &Log::builder("This is a log message with INFO severity") - /// .severity(LogSeverity::Info) - /// .source("file", 1) - /// .build(), - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits an Error symptom. - /// This method accepts a [`std::string::String`] to define the symptom. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error("symptom").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_error; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_error!(step, "symptom").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits an Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_msg("symptom", "error message").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_error; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_error!(step, "symptom", "error message").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method accepts a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_details( - /// &Error::builder("symptom") - /// .message("Error message") - /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) - /// .build(), - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_details( - &self, - error: &objects::Error, - ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits a Measurement message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.add_measurement("name", 50.into()).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement( - &self, - name: &str, - value: Value, - ) -> Result<(), emitters::WriterError> { - let measurement = objects::Measurement::new(name, value); - self.step - .state - .lock() - .await - .emitter - .emit(&measurement.to_artifact()) - .await?; - Ok(()) - } - - /// Emits a Measurement message. - /// This method accepts a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let hwinfo = HardwareInfo::builder("id", "fan").build(); - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let measurement = Measurement::builder("name", 5000.into()) - /// .hardware_info(&hwinfo) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) - /// .subcomponent(&Subcomponent::builder("name").build()) - /// .build(); - /// step.add_measurement_with_details(&measurement).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_details( - &self, - measurement: &objects::Measurement, - ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&measurement.to_artifact()) - .await?; - Ok(()) - } - - /// Starts 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. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// let series = step.measurement_series("name"); - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub fn measurement_series(&self, name: &str) -> MeasurementSeries { - self.measurement_id_no - .fetch_add(1, atomic::Ordering::SeqCst); - let series_id: String = format!( - "series_{}", - self.measurement_id_no.load(atomic::Ordering::SeqCst) - ); - - MeasurementSeries::new(&series_id, name, self.step.state.clone()) - } - - /// Starts a Measurement Series (a time-series list of measurements). - /// This method accepts a [`objects::MeasurementSeriesStart`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// let series = - /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub fn measurement_series_with_details( - &self, - start: objects::MeasurementSeriesStart, - ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, self.step.state.clone()) - } -} - -/// 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 -pub struct MeasurementSeries { - state: Arc>, - seq_no: Arc>, - start: objects::MeasurementSeriesStart, -} - -impl MeasurementSeries { - fn new(series_id: &str, name: &str, state: Arc>) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start: objects::MeasurementSeriesStart::new(name, series_id), - } - } - - fn new_with_details( - start: objects::MeasurementSeriesStart, - state: Arc>, - ) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start, - } - } - - async fn current_sequence_no(&self) -> u64 { - self.seq_no.lock().await.load(atomic::Ordering::SeqCst) - } - - async fn increment_sequence_no(&self) { - self.seq_no - .lock() - .await - .fetch_add(1, atomic::Ordering::SeqCst); - } - - /// Starts the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter - .emit(&self.start.to_artifact()) - .await?; - Ok(()) - } - - /// Ends the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.end().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { - let end = objects::MeasurementSeriesEnd::new( - self.start.get_series_id(), - self.current_sequence_no().await, - ); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement(60.into()).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - None, - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// This method accepts additional metadata to add to the element. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_metadata( - &self, - value: Value, - metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - Some(Map::from_iter( - metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), - )), - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::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 - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.scope(|s| async { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; - /// Ok(()) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a MeasurementSeries) -> R, - { - self.start().await?; - func(self).await?; - self.end().await?; - Ok(()) - } -} diff --git a/src/output/state.rs b/src/output/state.rs new file mode 100644 index 0000000..63164f8 --- /dev/null +++ b/src/output/state.rs @@ -0,0 +1,18 @@ +// (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 crate::output::emitters; + +// TODO: will prob need some redesign +pub struct TestState { + pub emitter: emitters::JsonEmitter, +} + +impl TestState { + pub fn new(emitter: emitters::JsonEmitter) -> TestState { + TestState { emitter } + } +} diff --git a/src/output/step.rs b/src/output/step.rs new file mode 100644 index 0000000..59b6ddb --- /dev/null +++ b/src/output/step.rs @@ -0,0 +1,507 @@ +// (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 serde_json::Value; +use std::sync::atomic; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::measurement_series::MeasurementSeries; +use tv::{emitters, models, objects, state}; + +/// A single test step in the scope of a [`TestRun`]. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +pub struct TestStep { + name: String, + state: Arc>, +} + +impl TestStep { + pub(crate) fn new(name: &str, state: Arc>) -> TestStep { + TestStep { + name: name.to_string(), + state, + } + } + + /// Starts the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(self) -> Result { + let start = objects::TestStepStart::new(&self.name); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + + Ok(StartedTestStep { + step: self, + measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), + }) + } + + // /// Builds a scope in the [`TestStep`] object, taking care of starting and + // /// ending it. View [`TestStep::start`] and [`TestStep::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 + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + // /// + // /// let step = run.step("first step")?; + // /// step.scope(|s| async { + // /// s.log( + // /// LogSeverity::Info, + // /// "This is a log message with INFO severity", + // /// ).await?; + // /// Ok(TestStatus::Complete) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // F: std::ops::FnOnce(&'a TestStep) -> R, + // { + // self.start().await?; + // let status = func(self).await?; + // self.end(status).await?; + // Ok(()) + // } +} + +pub struct StartedTestStep { + step: TestStep, + measurement_id_no: Arc, +} + +impl StartedTestStep { + /// Ends the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { + let end = objects::TestStepEnd::new(status); + self.step + .state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Eemits Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_log_info; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits an Error symptom. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error("symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_error!(step, "symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits an Error message. + /// This method accepts a [`std::string::String`] to define the symptom and + /// another [`std::string::String`] as error message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error_with_msg("symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_error!(step, "symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.add_measurement("name", 50.into()).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement( + &self, + name: &str, + value: Value, + ) -> Result<(), emitters::WriterError> { + let measurement = objects::Measurement::new(name, value); + self.step + .state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let hwinfo = HardwareInfo::builder("id", "fan").build(); + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let measurement = Measurement::builder("name", 5000.into()) + /// .hardware_info(&hwinfo) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_metadata("key", "value".into()) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// step.add_measurement_with_details(&measurement).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_details( + &self, + measurement: &objects::Measurement, + ) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Starts 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. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// let series = step.measurement_series("name"); + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub fn measurement_series(&self, name: &str) -> MeasurementSeries { + self.measurement_id_no + .fetch_add(1, atomic::Ordering::SeqCst); + let series_id: String = format!( + "series_{}", + self.measurement_id_no.load(atomic::Ordering::SeqCst) + ); + + MeasurementSeries::new(&series_id, name, self.step.state.clone()) + } + + /// Starts a Measurement Series (a time-series list of measurements). + /// This method accepts a [`objects::MeasurementSeriesStart`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// let series = + /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub fn measurement_series_with_details( + &self, + start: objects::MeasurementSeriesStart, + ) -> MeasurementSeries { + MeasurementSeries::new_with_details(start, self.step.state.clone()) + } +} From b67d9c2416b3873740b2c6165326d43d39cc0296 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 19:17:55 +0100 Subject: [PATCH 2/5] remove objects.rs - `objects.rs` was a mix of unrelated items, move them to separate modules for easier maintainability Signed-off-by: mimir-d --- src/output/dut.rs | 621 ++++++++++ src/output/emitters.rs | 6 +- src/output/error.rs | 232 ++++ src/output/log.rs | 130 ++ src/output/measurement.rs | 1009 ++++++++++++++++ src/output/measurement_series.rs | 241 ---- src/output/mod.rs | 14 +- src/output/objects.rs | 1920 ------------------------------ src/output/run.rs | 228 +++- src/output/step.rs | 77 +- 10 files changed, 2270 insertions(+), 2208 deletions(-) create mode 100644 src/output/dut.rs create mode 100644 src/output/error.rs create mode 100644 src/output/log.rs create mode 100644 src/output/measurement.rs delete mode 100644 src/output/measurement_series.rs delete mode 100644 src/output/objects.rs diff --git a/src/output/dut.rs b/src/output/dut.rs new file mode 100644 index 0000000..89679f2 --- /dev/null +++ b/src/output/dut.rs @@ -0,0 +1,621 @@ +// (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 crate::output as tv; +use tv::models; + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct DutInfo { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfo { + pub fn builder(id: &str) -> DutInfoBuilder { + DutInfoBuilder::new(id) + } + + pub fn new(id: &str) -> DutInfo { + DutInfoBuilder::new(id).build() + } + + pub(crate) fn to_spec(&self) -> models::DutInfoSpec { + models::DutInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + platform_infos: self + .platform_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + software_infos: self + .software_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + hardware_infos: self + .hardware_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + metadata: self.metadata.clone(), + } + } +} + +pub struct DutInfoBuilder { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfoBuilder { + pub fn new(id: &str) -> DutInfoBuilder { + DutInfoBuilder { + id: id.to_string(), + name: None, + platform_infos: None, + software_infos: None, + hardware_infos: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> DutInfoBuilder { + self.name = Some(value.to_string()); + self + } + + pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { + self.platform_infos = match self.platform_infos { + Some(mut platform_infos) => { + platform_infos.push(platform_info.clone()); + Some(platform_infos) + } + None => Some(vec![platform_info.clone()]), + }; + self + } + + pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.clone()); + Some(software_infos) + } + None => Some(vec![software_info.clone()]), + }; + self + } + + pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { + self.hardware_infos = match self.hardware_infos { + Some(mut hardware_infos) => { + hardware_infos.push(hardware_info.clone()); + Some(hardware_infos) + } + None => Some(vec![hardware_info.clone()]), + }; + self + } + + pub fn add_metadata(mut self, key: &str, value: serde_json::Value) -> DutInfoBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = serde_json::Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> DutInfo { + DutInfo { + id: self.id, + name: self.name, + platform_infos: self.platform_infos, + software_infos: self.software_infos, + hardware_infos: self.hardware_infos, + metadata: self.metadata, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct HardwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfo { + pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { + HardwareInfoBuilder::new(id, name) + } + + pub fn to_spec(&self) -> models::HardwareInfoSpec { + models::HardwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + location: self.location.clone(), + serial_no: self.serial_no.clone(), + part_no: self.part_no.clone(), + manufacturer: self.manufacturer.clone(), + manufacturer_part_no: self.manufacturer_part_no.clone(), + odata_id: self.odata_id.clone(), + computer_system: self.computer_system.clone(), + manager: self.manager.clone(), + } + } + + pub fn id(&self) -> &str { + &self.id + } +} + +#[derive(Debug)] +pub struct HardwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + HardwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + location: None, + serial_no: None, + part_no: None, + manufacturer: None, + manufacturer_part_no: None, + odata_id: None, + computer_system: None, + manager: None, + } + } + pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + self.location = Some(value.to_string()); + self + } + pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + self.serial_no = Some(value.to_string()); + self + } + pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.part_no = Some(value.to_string()); + self + } + pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer = Some(value.to_string()); + self + } + pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer_part_no = Some(value.to_string()); + self + } + pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + self.odata_id = Some(value.to_string()); + self + } + pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + self.manager = Some(value.to_string()); + self + } + + pub fn build(self) -> HardwareInfo { + HardwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + location: self.location, + serial_no: self.serial_no, + part_no: self.part_no, + manufacturer: self.manufacturer, + manufacturer_part_no: self.manufacturer_part_no, + odata_id: self.odata_id, + computer_system: self.computer_system, + manager: self.manager, + } + } +} + +#[derive(Debug, Clone)] +pub struct Subcomponent { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl Subcomponent { + pub fn builder(name: &str) -> SubcomponentBuilder { + SubcomponentBuilder::new(name) + } + pub fn to_spec(&self) -> models::SubcomponentSpec { + models::SubcomponentSpec { + subcomponent_type: self.subcomponent_type.clone(), + name: self.name.clone(), + location: self.location.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + } + } +} + +#[derive(Debug)] +pub struct SubcomponentBuilder { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl SubcomponentBuilder { + fn new(name: &str) -> Self { + SubcomponentBuilder { + subcomponent_type: None, + name: name.to_string(), + location: None, + version: None, + revision: None, + } + } + pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { + self.subcomponent_type = Some(value); + self + } + pub fn version(mut self, value: &str) -> SubcomponentBuilder { + self.version = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> SubcomponentBuilder { + self.location = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SubcomponentBuilder { + self.revision = Some(value.to_string()); + self + } + + pub fn build(self) -> Subcomponent { + Subcomponent { + subcomponent_type: self.subcomponent_type, + name: self.name, + location: self.location, + version: self.version, + revision: self.revision, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PlatformInfo { + info: String, +} + +impl PlatformInfo { + pub fn builder(info: &str) -> PlatformInfoBuilder { + PlatformInfoBuilder::new(info) + } + + pub fn to_spec(&self) -> models::PlatformInfoSpec { + models::PlatformInfoSpec { + info: self.info.clone(), + } + } +} + +#[derive(Debug)] +pub struct PlatformInfoBuilder { + info: String, +} + +impl PlatformInfoBuilder { + fn new(info: &str) -> Self { + PlatformInfoBuilder { + info: info.to_string(), + } + } + + pub fn build(self) -> PlatformInfo { + PlatformInfo { info: self.info } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SoftwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfo { + pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { + SoftwareInfoBuilder::new(id, name) + } + + pub fn to_spec(&self) -> models::SoftwareInfoSpec { + models::SoftwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + software_type: self.software_type.clone(), + computer_system: self.computer_system.clone(), + } + } +} + +#[derive(Debug)] +pub struct SoftwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + SoftwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + software_type: None, + computer_system: None, + } + } + pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { + self.software_type = Some(value); + self + } + pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + + pub fn build(self) -> SoftwareInfo { + SoftwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + software_type: self.software_type, + computer_system: self.computer_system, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output::models; + use anyhow::{bail, Result}; + + #[test] + fn test_dut_creation_from_builder_with_defaults() -> Result<()> { + let dut = DutInfo::builder("1234").build(); + assert_eq!(dut.id, "1234"); + Ok(()) + } + + #[test] + fn test_dut_builder() -> Result<()> { + let platform = PlatformInfo::builder("platform_info").build(); + let software = SoftwareInfo::builder("software_id", "name").build(); + let hardware = HardwareInfo::builder("hardware_id", "name").build(); + let dut = DutInfo::builder("1234") + .name("DUT") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_hardware_info(&hardware) + .add_hardware_info(&hardware) + .add_platform_info(&platform) + .add_platform_info(&platform) + .add_software_info(&software) + .add_software_info(&software) + .build(); + + let spec_dut = dut.to_spec(); + + assert_eq!(spec_dut.id, "1234"); + assert_eq!(spec_dut.name, Some("DUT".to_owned())); + + match spec_dut.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is empty"), + } + + match spec_dut.hardware_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "hardware_id"); + } + _ => bail!("hardware_infos is empty"), + }, + _ => bail!("hardware_infos is missing"), + } + + match spec_dut.software_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "software_id"); + } + _ => bail!("software_infos is empty"), + }, + _ => bail!("software_infos is missing"), + } + + match spec_dut.platform_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.info, "platform_info"); + } + _ => bail!("platform_infos is empty"), + }, + _ => bail!("platform_infos is missing"), + } + + Ok(()) + } + + #[test] + fn test_hardware_info() -> Result<()> { + let info = HardwareInfo::builder("hardware_id", "hardware_name") + .version("version") + .revision("revision") + .location("location") + .serial_no("serial_no") + .part_no("part_no") + .manufacturer("manufacturer") + .manufacturer_part_no("manufacturer_part_no") + .odata_id("odata_id") + .computer_system("computer_system") + .manager("manager") + .build(); + + let spec_hwinfo = info.to_spec(); + + assert_eq!(spec_hwinfo.id, "hardware_id"); + assert_eq!(spec_hwinfo.name, "hardware_name"); + assert_eq!(spec_hwinfo.version, Some("version".to_owned())); + assert_eq!(spec_hwinfo.revision, Some("revision".to_owned())); + assert_eq!(spec_hwinfo.location, Some("location".to_owned())); + assert_eq!(spec_hwinfo.serial_no, Some("serial_no".to_owned())); + assert_eq!(spec_hwinfo.part_no, Some("part_no".to_owned())); + assert_eq!(spec_hwinfo.manufacturer, Some("manufacturer".to_owned())); + assert_eq!( + spec_hwinfo.manufacturer_part_no, + Some("manufacturer_part_no".to_owned()) + ); + assert_eq!(spec_hwinfo.odata_id, Some("odata_id".to_owned())); + assert_eq!( + spec_hwinfo.computer_system, + Some("computer_system".to_owned()) + ); + assert_eq!(spec_hwinfo.manager, Some("manager".to_owned())); + + Ok(()) + } + + #[test] + fn test_software_info() -> Result<()> { + let info = SoftwareInfo::builder("software_id", "name") + .version("version") + .revision("revision") + .software_type(models::SoftwareType::Application) + .computer_system("system") + .build(); + + let spec_swinfo = info.to_spec(); + + assert_eq!(spec_swinfo.id, "software_id"); + assert_eq!(spec_swinfo.name, "name"); + assert_eq!(spec_swinfo.version, Some("version".to_owned())); + assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); + assert_eq!( + spec_swinfo.software_type, + Some(models::SoftwareType::Application) + ); + assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); + + Ok(()) + } + + #[test] + fn test_platform_info() -> Result<()> { + let info = PlatformInfo::builder("info").build(); + + assert_eq!(info.to_spec().info, "info"); + Ok(()) + } + + #[test] + fn test_subcomponent() -> Result<()> { + let sub = Subcomponent::builder("sub_name") + .subcomponent_type(models::SubcomponentType::Asic) + .version("version") + .location("location") + .revision("revision") + .build(); + + let spec_subcomponent = sub.to_spec(); + + assert_eq!(spec_subcomponent.name, "sub_name"); + assert_eq!(spec_subcomponent.version, Some("version".to_owned())); + assert_eq!(spec_subcomponent.revision, Some("revision".to_owned())); + assert_eq!(spec_subcomponent.location, Some("location".to_owned())); + assert_eq!( + spec_subcomponent.subcomponent_type, + Some(models::SubcomponentType::Asic) + ); + + Ok(()) + } +} diff --git a/src/output/emitters.rs b/src/output/emitters.rs index f33bb71..d818b65 100644 --- a/src/output/emitters.rs +++ b/src/output/emitters.rs @@ -127,13 +127,13 @@ impl JsonEmitter { #[cfg(test)] mod tests { - use anyhow::anyhow; - use anyhow::Result; + use anyhow::{anyhow, Result}; use assert_json_diff::assert_json_include; use serde_json::json; use super::*; - use crate::output::objects::*; + use crate::output as tv; + use tv::run::SchemaVersion; #[tokio::test] async fn test_emit_using_buffer_writer() -> Result<()> { diff --git a/src/output/error.rs b/src/output/error.rs new file mode 100644 index 0000000..d61e9a0 --- /dev/null +++ b/src/output/error.rs @@ -0,0 +1,232 @@ +// (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 crate::output as tv; +use tv::{dut, models, run::ArtifactContext}; + +pub struct Error { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl Error { + pub fn builder(symptom: &str) -> ErrorBuilder { + ErrorBuilder::new(symptom) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct ErrorBuilder { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl ErrorBuilder { + fn new(symptom: &str) -> Self { + ErrorBuilder { + symptom: symptom.to_string(), + message: None, + source_location: None, + software_infos: None, + } + } + pub fn message(mut self, value: &str) -> ErrorBuilder { + self.message = Some(value.to_string()); + self + } + pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + pub fn add_software_info(mut self, software_info: &dut::SoftwareInfo) -> ErrorBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.to_spec()); + Some(software_infos) + } + None => Some(vec![software_info.to_spec()]), + }; + self + } + + pub fn build(self) -> Error { + Error { + symptom: self.symptom, + message: self.message, + source_location: self.source_location, + software_infos: self.software_infos, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use assert_json_diff::assert_json_include; + + use super::*; + use crate::output as tv; + use tv::{dut, models}; + + #[test] + fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") + .message("") + .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + + let artifact = error.to_artifact(ArtifactContext::TestRun); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") + .message("") + .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + + let artifact = error.to_artifact(ArtifactContext::TestStep); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_error() -> Result<()> { + let expected_run = serde_json::json!({ + "testRunArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file": "file.rs", "line": 1}, + "symptom": "symptom" + } + } + }); + let expected_step = serde_json::json!({ + "testStepArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file":"file.rs","line":1}, + "symptom":"symptom" + } + } + }); + + let software = dut::SoftwareInfo::builder("software_id", "name").build(); + let error = ErrorBuilder::new("symptom") + .message("message") + .source("file.rs", 1) + .add_software_info(&software) + .add_software_info(&software) + .build(); + + let spec_error = error.to_artifact(ArtifactContext::TestRun); + let actual = serde_json::json!(spec_error); + assert_json_include!(actual: actual, expected: &expected_run); + + let spec_error = error.to_artifact(ArtifactContext::TestStep); + let actual = serde_json::json!(spec_error); + assert_json_include!(actual: actual, expected: &expected_step); + + Ok(()) + } +} diff --git a/src/output/log.rs b/src/output/log.rs new file mode 100644 index 0000000..f76af11 --- /dev/null +++ b/src/output/log.rs @@ -0,0 +1,130 @@ +// (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 crate::output as tv; +use tv::{models, run::ArtifactContext}; + +pub struct Log { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl Log { + pub fn builder(message: &str) -> LogBuilder { + LogBuilder::new(message) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct LogBuilder { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl LogBuilder { + fn new(message: &str) -> Self { + LogBuilder { + severity: models::LogSeverity::Info, + message: message.to_string(), + source_location: None, + } + } + pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { + self.severity = value; + self + } + pub fn source(mut self, file: &str, line: i32) -> LogBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + + pub fn build(self) -> Log { + Log { + severity: self.severity, + message: self.message, + source_location: self.source_location, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::output as tv; + use tv::models; + + #[test] + fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) + .build(); + + let artifact = log.to_artifact(ArtifactContext::TestRun); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) + .build(); + + let artifact = log.to_artifact(ArtifactContext::TestStep); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) + ); + + Ok(()) + } +} diff --git a/src/output/measurement.rs b/src/output/measurement.rs new file mode 100644 index 0000000..c641d8b --- /dev/null +++ b/src/output/measurement.rs @@ -0,0 +1,1009 @@ +// (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::future::Future; +use std::sync::atomic; +use std::sync::Arc; + +use chrono::DateTime; +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::{dut, emitters, models, state}; + +/// 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 +pub struct MeasurementSeries { + state: Arc>, + seq_no: Arc>, + start: MeasurementSeriesStart, +} + +impl MeasurementSeries { + pub(crate) fn new(series_id: &str, name: &str, state: Arc>) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start: MeasurementSeriesStart::new(name, series_id), + } + } + + pub(crate) fn new_with_details( + start: MeasurementSeriesStart, + state: Arc>, + ) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start, + } + } + + async fn current_sequence_no(&self) -> u64 { + self.seq_no.lock().await.load(atomic::Ordering::SeqCst) + } + + async fn increment_sequence_no(&self) { + self.seq_no + .lock() + .await + .fetch_add(1, atomic::Ordering::SeqCst); + } + + /// Starts the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&self.start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.end().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self) -> Result<(), emitters::WriterError> { + let end = + MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement(60.into()).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + let element = MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + None, + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// This method accepts additional metadata to add to the element. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_metadata( + &self, + value: Value, + metadata: Vec<(&str, Value)>, + ) -> Result<(), emitters::WriterError> { + let element = MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + Some(Map::from_iter( + metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), + )), + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::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 + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.scope(|s| async { + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; + /// Ok(()) + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + { + self.start().await?; + func(self).await?; + self.end().await?; + Ok(()) + } +} + +#[derive(Clone)] +pub struct Validator { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl Validator { + pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { + ValidatorBuilder::new(validator_type, value) + } + pub fn to_spec(&self) -> models::ValidatorSpec { + models::ValidatorSpec { + name: self.name.clone(), + validator_type: self.validator_type.clone(), + value: self.value.clone(), + metadata: self.metadata.clone(), + } + } +} + +#[derive(Debug)] +pub struct ValidatorBuilder { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl ValidatorBuilder { + fn new(validator_type: models::ValidatorType, value: Value) -> Self { + ValidatorBuilder { + validator_type, + value: value.clone(), + name: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> ValidatorBuilder { + self.name = Some(value.to_string()); + self + } + pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> Validator { + Validator { + name: self.name, + validator_type: self.validator_type, + value: self.value, + metadata: self.metadata, + } + } +} + +/// This structure represents a Measurement message. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// +/// # Examples +/// +/// ## Create a Measurement object with the `new` method +/// +/// ``` +/// use ocptv::output::Measurement; +/// use ocptv::output::Value; +/// +/// let measurement = Measurement::new("name", 50.into()); +/// ``` +/// +/// ## Create a Measurement object with the `builder` method +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; +/// +/// let measurement = Measurement::builder("name", 50.into()) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) +/// .subcomponent(&Subcomponent::builder("name").build()) +/// .build(); +/// ``` +pub struct Measurement { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl Measurement { + /// Builds a new Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::new("name", 50.into()); + /// ``` + pub fn new(name: &str, value: Value) -> Self { + Measurement { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Builds a new Measurement object using [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::Measurement; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::builder("name", 50.into()) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_metadata("key", "value".into()) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// ``` + pub fn builder(name: &str, value: Value) -> MeasurementBuilder { + MeasurementBuilder::new(name, value) + } + + /// Creates an artifact from a Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::new("name", 50.into()); + /// let _ = measurement.to_artifact(); + /// ``` + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { + 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()), + hardware_info_id: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.id().to_owned()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }), + }) + } +} + +/// This structure builds a [`Measurement`] object. +/// +/// # Examples +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::MeasurementBuilder; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; +/// +/// let builder = MeasurementBuilder::new("name", 50.into()) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) +/// .subcomponent(&Subcomponent::builder("name").build()); +/// let measurement = builder.build(); +/// ``` +pub struct MeasurementBuilder { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +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: Value) -> Self { + MeasurementBuilder { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Add a [`Validator`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("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()]), + }; + self + } + + /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()); + /// ``` + pub fn hardware_info(mut self, hardware_info: &dut::HardwareInfo) -> MeasurementBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + /// Add a [`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()); + /// ``` + pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> MeasurementBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + /// Add custom metadata to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = + /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { + match self.metadata { + Some(ref mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + } + None => { + let mut entries = serde_json::Map::new(); + entries.insert(key.to_owned(), value); + self.metadata = Some(entries); + } + }; + self + } + + /// Add measurement unit to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); + /// ``` + pub fn unit(mut self, unit: &str) -> MeasurementBuilder { + self.unit = Some(unit.to_string()); + self + } + + /// Builds a [`Measurement`] object from a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()); + /// let measurement = builder.build(); + /// ``` + pub fn build(self) -> Measurement { + Measurement { + name: self.name, + value: self.value, + unit: self.unit, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesStart { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStart { + pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { + MeasurementSeriesStartBuilder::new(name, series_id) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: self.name.clone(), + unit: self.unit.clone(), + series_id: self.series_id.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.to_spec()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }, + ), + }) + } + + pub fn get_series_id(&self) -> &str { + &self.series_id + } +} + +pub struct MeasurementSeriesStartBuilder { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStartBuilder { + pub fn new(name: &str, series_id: &str) -> Self { + MeasurementSeriesStartBuilder { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { + self.validators = match self.validators { + Some(mut validators) => { + validators.push(validator.clone()); + Some(validators) + } + None => Some(vec![validator.clone()]), + }; + self + } + + pub fn hardware_info( + mut self, + hardware_info: &dut::HardwareInfo, + ) -> MeasurementSeriesStartBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + pub fn subcomponent( + mut self, + subcomponent: &dut::Subcomponent, + ) -> MeasurementSeriesStartBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { + self.unit = Some(unit.to_string()); + self + } + + pub fn build(self) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: self.name, + unit: self.unit, + series_id: self.series_id, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesEnd { + series_id: String, + total_count: u64, +} + +impl MeasurementSeriesEnd { + pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { + MeasurementSeriesEnd { + series_id: series_id.to_string(), + total_count, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: self.series_id.clone(), + total_count: self.total_count, + }, + ), + }) + } +} + +pub struct MeasurementSeriesElement { + index: u64, + value: Value, + timestamp: DateTime, + series_id: String, + metadata: Option>, +} + +impl MeasurementSeriesElement { + pub(crate) fn new( + index: u64, + value: Value, + series: &MeasurementSeriesStart, + metadata: Option>, + ) -> MeasurementSeriesElement { + MeasurementSeriesElement { + index, + value: value.clone(), + timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + series_id: series.series_id.to_string(), + metadata, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( + models::MeasurementSeriesElementSpec { + index: self.index, + value: self.value.clone(), + timestamp: self.timestamp, + series_id: self.series_id.clone(), + metadata: self.metadata.clone(), + }, + ), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output as tv; + use tv::ValidatorType; + use tv::{dut::*, models}; + + use anyhow::{bail, Result}; + + #[test] + fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let value = Value::from(50); + let measurement = Measurement::new(&name, value.clone()); + + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let value = Value::from(50000); + let hardware_info = HardwareInfo::builder("id", "name").build(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + + let meta_key = "key"; + let meta_value = Value::from("value"); + let mut metadata = Map::new(); + metadata.insert(meta_key.to_string(), meta_value.clone()); + metadata.insert(meta_key.to_string(), meta_value.clone()); + + let subcomponent = Subcomponent::builder("name").build(); + + let unit = "RPM"; + let measurement = Measurement::builder(&name, value.clone()) + .hardware_info(&hardware_info) + .add_validator(&validator) + .add_validator(&validator) + .add_metadata(meta_key, meta_value.clone()) + .add_metadata(meta_key, meta_value.clone()) + .subcomponent(&subcomponent) + .unit(unit) + .build(); + + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name, + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_start_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesStart::new(&name, &series_id); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_start_builder_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); + let hw_info = HardwareInfo::builder("id", "name").build(); + let subcomponent = Subcomponent::builder("name").build(); + let series = MeasurementSeriesStart::builder(&name, &series_id) + .unit("unit") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_validator(&validator) + .add_validator(&validator2) + .hardware_info(&hw_info) + .subcomponent(&subcomponent) + .build(); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name, + unit: Some("unit".to_string()), + series_id: series_id.to_string(), + validators: Some(vec![validator.to_spec(), validator2.to_spec()]), + hardware_info: Some(hw_info.to_spec()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(serde_json::Map::from_iter([ + ("key".to_string(), "value".into()), + ("key2".to_string(), "value2".into()) + ])), + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_end_to_artifact() -> Result<()> { + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesEnd::new(&series_id, 1); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: series_id.to_string(), + total_count: 1, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_validator() -> Result<()> { + let validator = Validator::builder(ValidatorType::Equal, 30.into()) + .name("validator") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .build(); + + let spec_validator = validator.to_spec(); + + assert_eq!(spec_validator.name, Some("validator".to_owned())); + assert_eq!(spec_validator.value, 30); + assert_eq!(spec_validator.validator_type, ValidatorType::Equal); + + match spec_validator.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is none"), + } + + Ok(()) + } +} diff --git a/src/output/measurement_series.rs b/src/output/measurement_series.rs deleted file mode 100644 index c7372c2..0000000 --- a/src/output/measurement_series.rs +++ /dev/null @@ -1,241 +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::future::Future; -use std::sync::atomic; -use std::sync::Arc; - -use serde_json::Map; -use serde_json::Value; -use tokio::sync::Mutex; - -use crate::output as tv; -use tv::{emitters, objects, state}; - -/// 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 -pub struct MeasurementSeries { - state: Arc>, - seq_no: Arc>, - start: objects::MeasurementSeriesStart, -} - -impl MeasurementSeries { - pub fn new(series_id: &str, name: &str, state: Arc>) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start: objects::MeasurementSeriesStart::new(name, series_id), - } - } - - pub fn new_with_details( - start: objects::MeasurementSeriesStart, - state: Arc>, - ) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start, - } - } - - async fn current_sequence_no(&self) -> u64 { - self.seq_no.lock().await.load(atomic::Ordering::SeqCst) - } - - async fn increment_sequence_no(&self) { - self.seq_no - .lock() - .await - .fetch_add(1, atomic::Ordering::SeqCst); - } - - /// Starts the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter - .emit(&self.start.to_artifact()) - .await?; - Ok(()) - } - - /// Ends the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.end().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { - let end = objects::MeasurementSeriesEnd::new( - self.start.get_series_id(), - self.current_sequence_no().await, - ); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement(60.into()).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - None, - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// This method accepts additional metadata to add to the element. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_metadata( - &self, - value: Value, - metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - Some(Map::from_iter( - metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), - )), - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::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 - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.scope(|s| async { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; - /// Ok(()) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a MeasurementSeries) -> R, - { - self.start().await?; - func(self).await?; - self.end().await?; - Ok(()) - } -} diff --git a/src/output/mod.rs b/src/output/mod.rs index c0d0a6e..d296c57 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -5,23 +5,29 @@ // https://opensource.org/licenses/MIT. mod config; +mod dut; mod emitters; +mod error; +mod log; mod macros; -mod measurement_series; +mod measurement; mod models; -mod objects; mod run; mod state; mod step; pub use config::*; +pub use dut::*; pub use emitters::*; +pub use error::*; +pub use log::*; +pub use measurement::*; pub use models::LogSeverity; pub use models::TestResult; pub use models::TestStatus; pub use models::ValidatorType; pub use models::SPEC_VERSION; -pub use objects::*; pub use run::*; -pub use serde_json::Value; pub use step::*; + +pub use serde_json::Value; diff --git a/src/output/objects.rs b/src/output/objects.rs deleted file mode 100644 index 8aa115c..0000000 --- a/src/output/objects.rs +++ /dev/null @@ -1,1920 +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 chrono::DateTime; -use serde_json::Map; -use serde_json::Value; - -use crate::output::models; - -pub enum ArtifactContext { - TestRun, - TestStep, -} - -pub struct SchemaVersion { - major: i8, - minor: i8, -} - -#[allow(clippy::new_without_default)] -impl SchemaVersion { - pub fn new() -> SchemaVersion { - SchemaVersion { - major: models::SPEC_VERSION.0, - minor: models::SPEC_VERSION.1, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { - major: self.major, - minor: self.minor, - }) - } -} - -#[derive(Default, Debug, Clone, PartialEq)] -pub struct DutInfo { - id: String, - name: Option, - platform_infos: Option>, - software_infos: Option>, - hardware_infos: Option>, - metadata: Option>, -} - -impl DutInfo { - pub fn builder(id: &str) -> DutInfoBuilder { - DutInfoBuilder::new(id) - } - - pub fn new(id: &str) -> DutInfo { - DutInfoBuilder::new(id).build() - } - - pub(crate) fn to_spec(&self) -> models::DutInfoSpec { - models::DutInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - platform_infos: self - .platform_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - software_infos: self - .software_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - hardware_infos: self - .hardware_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - metadata: self.metadata.clone(), - } - } -} - -pub struct DutInfoBuilder { - id: String, - name: Option, - platform_infos: Option>, - software_infos: Option>, - hardware_infos: Option>, - metadata: Option>, -} - -impl DutInfoBuilder { - pub fn new(id: &str) -> DutInfoBuilder { - DutInfoBuilder { - id: id.to_string(), - name: None, - platform_infos: None, - software_infos: None, - hardware_infos: None, - metadata: None, - } - } - pub fn name(mut self, value: &str) -> DutInfoBuilder { - self.name = Some(value.to_string()); - self - } - - pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { - self.platform_infos = match self.platform_infos { - Some(mut platform_infos) => { - platform_infos.push(platform_info.clone()); - Some(platform_infos) - } - None => Some(vec![platform_info.clone()]), - }; - self - } - - pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.clone()); - Some(software_infos) - } - None => Some(vec![software_info.clone()]), - }; - self - } - - pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { - self.hardware_infos = match self.hardware_infos { - Some(mut hardware_infos) => { - hardware_infos.push(hardware_info.clone()); - Some(hardware_infos) - } - None => Some(vec![hardware_info.clone()]), - }; - self - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> DutInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> DutInfo { - DutInfo { - id: self.id, - name: self.name, - platform_infos: self.platform_infos, - software_infos: self.software_infos, - hardware_infos: self.hardware_infos, - metadata: self.metadata, - } - } -} - -pub struct TestRunStart { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: DutInfo, -} - -impl TestRunStart { - pub fn builder( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { - name: self.name.clone(), - version: self.version.clone(), - command_line: self.command_line.clone(), - parameters: self.parameters.clone(), - metadata: self.metadata.clone(), - dut_info: self.dut_info.to_spec(), - }), - }) - } -} - -pub struct TestRunStartBuilder { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: DutInfo, -} - -impl TestRunStartBuilder { - pub fn new( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder { - name: name.to_string(), - version: version.to_string(), - command_line: command_line.to_string(), - parameters: parameters.clone(), - metadata: None, - dut_info: dut_info.clone(), - } - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRunStart { - TestRunStart { - name: self.name, - version: self.version, - command_line: self.command_line, - parameters: self.parameters, - metadata: self.metadata, - dut_info: self.dut_info, - } - } -} - -pub struct TestRunEnd { - status: models::TestStatus, - result: models::TestResult, -} - -impl TestRunEnd { - pub fn builder() -> TestRunEndBuilder { - TestRunEndBuilder::new() - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { - status: self.status.clone(), - result: self.result.clone(), - }), - }) - } -} - -#[derive(Debug)] -pub struct TestRunEndBuilder { - status: models::TestStatus, - result: models::TestResult, -} - -#[allow(clippy::new_without_default)] -impl TestRunEndBuilder { - pub fn new() -> TestRunEndBuilder { - TestRunEndBuilder { - status: models::TestStatus::Complete, - result: models::TestResult::Pass, - } - } - pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { - self.status = value; - self - } - - pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { - self.result = value; - self - } - - pub fn build(self) -> TestRunEnd { - TestRunEnd { - status: self.status, - result: self.result, - } - } -} - -pub struct Log { - severity: models::LogSeverity, - message: String, - source_location: Option, -} - -impl Log { - pub fn builder(message: &str) -> LogBuilder { - LogBuilder::new(message) - } - - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - } - } -} - -#[derive(Debug)] -pub struct LogBuilder { - severity: models::LogSeverity, - message: String, - source_location: Option, -} - -impl LogBuilder { - fn new(message: &str) -> Self { - LogBuilder { - severity: models::LogSeverity::Info, - message: message.to_string(), - source_location: None, - } - } - pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { - self.severity = value; - self - } - pub fn source(mut self, file: &str, line: i32) -> LogBuilder { - self.source_location = Some(models::SourceLocationSpec { - file: file.to_string(), - line, - }); - self - } - - pub fn build(self) -> Log { - Log { - severity: self.severity, - message: self.message, - source_location: self.source_location, - } - } -} - -pub struct Error { - symptom: String, - message: Option, - software_infos: Option>, - source_location: Option, -} - -impl Error { - pub fn builder(symptom: &str) -> ErrorBuilder { - ErrorBuilder::new(symptom) - } - - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - } - } -} - -#[derive(Debug)] -pub struct ErrorBuilder { - symptom: String, - message: Option, - software_infos: Option>, - source_location: Option, -} - -impl ErrorBuilder { - fn new(symptom: &str) -> Self { - ErrorBuilder { - symptom: symptom.to_string(), - message: None, - source_location: None, - software_infos: None, - } - } - pub fn message(mut self, value: &str) -> ErrorBuilder { - self.message = Some(value.to_string()); - self - } - pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { - self.source_location = Some(models::SourceLocationSpec { - file: file.to_string(), - line, - }); - self - } - pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> ErrorBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.to_spec()); - Some(software_infos) - } - None => Some(vec![software_info.to_spec()]), - }; - self - } - - pub fn build(self) -> Error { - Error { - symptom: self.symptom, - message: self.message, - source_location: self.source_location, - software_infos: self.software_infos, - } - } -} - -pub struct TestStepStart { - name: String, -} - -impl TestStepStart { - pub fn new(name: &str) -> TestStepStart { - TestStepStart { - name: name.to_string(), - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepStart( - models::TestStepStartSpec { - name: self.name.clone(), - }, - ), - }) - } -} - -pub struct TestStepEnd { - status: models::TestStatus, -} - -impl TestStepEnd { - pub fn new(status: models::TestStatus) -> TestStepEnd { - TestStepEnd { status } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { - status: self.status.clone(), - }), - }) - } -} - -#[derive(Clone)] -pub struct Validator { - name: Option, - validator_type: models::ValidatorType, - value: Value, - metadata: Option>, -} - -impl Validator { - pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { - ValidatorBuilder::new(validator_type, value) - } - pub fn to_spec(&self) -> models::ValidatorSpec { - models::ValidatorSpec { - name: self.name.clone(), - validator_type: self.validator_type.clone(), - value: self.value.clone(), - metadata: self.metadata.clone(), - } - } -} - -#[derive(Debug)] -pub struct ValidatorBuilder { - name: Option, - validator_type: models::ValidatorType, - value: Value, - metadata: Option>, -} - -impl ValidatorBuilder { - fn new(validator_type: models::ValidatorType, value: Value) -> Self { - ValidatorBuilder { - validator_type, - value: value.clone(), - name: None, - metadata: None, - } - } - pub fn name(mut self, value: &str) -> ValidatorBuilder { - self.name = Some(value.to_string()); - self - } - pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> Validator { - Validator { - name: self.name, - validator_type: self.validator_type, - value: self.value, - metadata: self.metadata, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct HardwareInfo { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfo { - pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { - HardwareInfoBuilder::new(id, name) - } - pub fn to_spec(&self) -> models::HardwareInfoSpec { - models::HardwareInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - location: self.location.clone(), - serial_no: self.serial_no.clone(), - part_no: self.part_no.clone(), - manufacturer: self.manufacturer.clone(), - manufacturer_part_no: self.manufacturer_part_no.clone(), - odata_id: self.odata_id.clone(), - computer_system: self.computer_system.clone(), - manager: self.manager.clone(), - } - } -} - -#[derive(Debug)] -pub struct HardwareInfoBuilder { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { - HardwareInfoBuilder { - id: id.to_string(), - name: name.to_string(), - version: None, - revision: None, - location: None, - serial_no: None, - part_no: None, - manufacturer: None, - manufacturer_part_no: None, - odata_id: None, - computer_system: None, - manager: None, - } - } - pub fn version(mut self, value: &str) -> HardwareInfoBuilder { - self.version = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { - self.revision = Some(value.to_string()); - self - } - pub fn location(mut self, value: &str) -> HardwareInfoBuilder { - self.location = Some(value.to_string()); - self - } - pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { - self.serial_no = Some(value.to_string()); - self - } - pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.part_no = Some(value.to_string()); - self - } - pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer = Some(value.to_string()); - self - } - pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer_part_no = Some(value.to_string()); - self - } - pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { - self.odata_id = Some(value.to_string()); - self - } - pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { - self.computer_system = Some(value.to_string()); - self - } - pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { - self.manager = Some(value.to_string()); - self - } - - pub fn build(self) -> HardwareInfo { - HardwareInfo { - id: self.id, - name: self.name, - version: self.version, - revision: self.revision, - location: self.location, - serial_no: self.serial_no, - part_no: self.part_no, - manufacturer: self.manufacturer, - manufacturer_part_no: self.manufacturer_part_no, - odata_id: self.odata_id, - computer_system: self.computer_system, - manager: self.manager, - } - } -} - -#[derive(Debug, Clone)] -pub struct Subcomponent { - subcomponent_type: Option, - name: String, - location: Option, - version: Option, - revision: Option, -} - -impl Subcomponent { - pub fn builder(name: &str) -> SubcomponentBuilder { - SubcomponentBuilder::new(name) - } - pub fn to_spec(&self) -> models::SubcomponentSpec { - models::SubcomponentSpec { - subcomponent_type: self.subcomponent_type.clone(), - name: self.name.clone(), - location: self.location.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - } - } -} - -#[derive(Debug)] -pub struct SubcomponentBuilder { - subcomponent_type: Option, - name: String, - location: Option, - version: Option, - revision: Option, -} - -impl SubcomponentBuilder { - fn new(name: &str) -> Self { - SubcomponentBuilder { - subcomponent_type: None, - name: name.to_string(), - location: None, - version: None, - revision: None, - } - } - pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { - self.subcomponent_type = Some(value); - self - } - pub fn version(mut self, value: &str) -> SubcomponentBuilder { - self.version = Some(value.to_string()); - self - } - pub fn location(mut self, value: &str) -> SubcomponentBuilder { - self.location = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> SubcomponentBuilder { - self.revision = Some(value.to_string()); - self - } - - pub fn build(self) -> Subcomponent { - Subcomponent { - subcomponent_type: self.subcomponent_type, - name: self.name, - location: self.location, - version: self.version, - revision: self.revision, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct PlatformInfo { - info: String, -} - -impl PlatformInfo { - pub fn builder(info: &str) -> PlatformInfoBuilder { - PlatformInfoBuilder::new(info) - } - - pub fn to_spec(&self) -> models::PlatformInfoSpec { - models::PlatformInfoSpec { - info: self.info.clone(), - } - } -} - -#[derive(Debug)] -pub struct PlatformInfoBuilder { - info: String, -} - -impl PlatformInfoBuilder { - fn new(info: &str) -> Self { - PlatformInfoBuilder { - info: info.to_string(), - } - } - - pub fn build(self) -> PlatformInfo { - PlatformInfo { info: self.info } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SoftwareInfo { - id: String, - name: String, - version: Option, - revision: Option, - software_type: Option, - computer_system: Option, -} - -impl SoftwareInfo { - pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { - SoftwareInfoBuilder::new(id, name) - } - - pub fn to_spec(&self) -> models::SoftwareInfoSpec { - models::SoftwareInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - software_type: self.software_type.clone(), - computer_system: self.computer_system.clone(), - } - } -} - -#[derive(Debug)] -pub struct SoftwareInfoBuilder { - id: String, - name: String, - version: Option, - revision: Option, - software_type: Option, - computer_system: Option, -} - -impl SoftwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { - SoftwareInfoBuilder { - id: id.to_string(), - name: name.to_string(), - version: None, - revision: None, - software_type: None, - computer_system: None, - } - } - pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { - self.version = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { - self.revision = Some(value.to_string()); - self - } - pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { - self.software_type = Some(value); - self - } - pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { - self.computer_system = Some(value.to_string()); - self - } - - pub fn build(self) -> SoftwareInfo { - SoftwareInfo { - id: self.id, - name: self.name, - version: self.version, - revision: self.revision, - software_type: self.software_type, - computer_system: self.computer_system, - } - } -} - -/// This structure represents a Measurement message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement -/// -/// # Examples -/// -/// ## Create a Measurement object with the `new` method -/// -/// ``` -/// use ocptv::output::Measurement; -/// use ocptv::output::Value; -/// -/// let measurement = Measurement::new("name", 50.into()); -/// ``` -/// -/// ## Create a Measurement object with the `builder` method -/// -/// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; -/// -/// let measurement = Measurement::builder("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) -/// .subcomponent(&Subcomponent::builder("name").build()) -/// .build(); -/// ``` -pub struct Measurement { - name: String, - value: Value, - unit: Option, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl Measurement { - /// Builds a new Measurement object. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::new("name", 50.into()); - /// ``` - pub fn new(name: &str, value: Value) -> Self { - Measurement { - name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - /// Builds a new Measurement object using [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::Measurement; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::builder("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) - /// .subcomponent(&Subcomponent::builder("name").build()) - /// .build(); - /// ``` - pub fn builder(name: &str, value: Value) -> MeasurementBuilder { - MeasurementBuilder::new(name, value) - } - - /// Creates an artifact from a Measurement object. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::new("name", 50.into()); - /// let _ = measurement.to_artifact(); - /// ``` - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { - 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()), - hardware_info_id: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.id.clone()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }), - }) - } -} - -/// This structure builds a [`Measurement`] object. -/// -/// # Examples -/// -/// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::MeasurementBuilder; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; -/// -/// let builder = MeasurementBuilder::new("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) -/// .subcomponent(&Subcomponent::builder("name").build()); -/// let measurement = builder.build(); -/// ``` -pub struct MeasurementBuilder { - name: String, - value: Value, - unit: Option, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -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: Value) -> Self { - MeasurementBuilder { - name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - /// Add a [`Validator`] to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("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()]), - }; - self - } - - /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()); - /// ``` - pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementBuilder { - self.hardware_info = Some(hardware_info.clone()); - self - } - - /// Add a [`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()); - /// ``` - pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementBuilder { - self.subcomponent = Some(subcomponent.clone()); - self - } - - /// Add custom metadata to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = - /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); - /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { - match self.metadata { - Some(ref mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - } - None => { - let mut entries = serde_json::Map::new(); - entries.insert(key.to_owned(), value); - self.metadata = Some(entries); - } - }; - self - } - - /// Add measurement unit to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); - /// ``` - pub fn unit(mut self, unit: &str) -> MeasurementBuilder { - self.unit = Some(unit.to_string()); - self - } - - /// Builds a [`Measurement`] object from a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); - /// let measurement = builder.build(); - /// ``` - pub fn build(self) -> Measurement { - Measurement { - name: self.name, - value: self.value, - unit: self.unit, - validators: self.validators, - hardware_info: self.hardware_info, - subcomponent: self.subcomponent, - metadata: self.metadata, - } - } -} - -pub struct MeasurementSeriesStart { - name: String, - unit: Option, - series_id: String, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl MeasurementSeriesStart { - pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { - MeasurementSeriesStart { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { - MeasurementSeriesStartBuilder::new(name, series_id) - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name: self.name.clone(), - unit: self.unit.clone(), - series_id: self.series_id.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.to_spec()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }, - ), - }) - } - - pub fn get_series_id(&self) -> &str { - &self.series_id - } -} - -pub struct MeasurementSeriesStartBuilder { - name: String, - unit: Option, - series_id: String, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl MeasurementSeriesStartBuilder { - pub fn new(name: &str, series_id: &str) -> Self { - MeasurementSeriesStartBuilder { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; - self - } - - pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementSeriesStartBuilder { - self.hardware_info = Some(hardware_info.clone()); - self - } - - pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementSeriesStartBuilder { - self.subcomponent = Some(subcomponent.clone()); - self - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { - self.unit = Some(unit.to_string()); - self - } - - pub fn build(self) -> MeasurementSeriesStart { - MeasurementSeriesStart { - name: self.name, - unit: self.unit, - series_id: self.series_id, - validators: self.validators, - hardware_info: self.hardware_info, - subcomponent: self.subcomponent, - metadata: self.metadata, - } - } -} - -pub struct MeasurementSeriesEnd { - series_id: String, - total_count: u64, -} - -impl MeasurementSeriesEnd { - pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { - MeasurementSeriesEnd { - series_id: series_id.to_string(), - total_count, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { - series_id: self.series_id.clone(), - total_count: self.total_count, - }, - ), - }) - } -} - -pub struct MeasurementSeriesElement { - index: u64, - value: Value, - timestamp: DateTime, - series_id: String, - metadata: Option>, -} - -impl MeasurementSeriesElement { - pub(crate) fn new( - index: u64, - value: Value, - series: &MeasurementSeriesStart, - metadata: Option>, - ) -> MeasurementSeriesElement { - MeasurementSeriesElement { - index, - value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), - series_id: series.series_id.to_string(), - metadata, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( - models::MeasurementSeriesElementSpec { - index: self.index, - value: self.value.clone(), - timestamp: self.timestamp, - series_id: self.series_id.clone(), - metadata: self.metadata.clone(), - }, - ), - }) - } -} - -#[cfg(test)] -mod tests { - use anyhow::bail; - use anyhow::Result; - - use assert_json_diff::assert_json_include; - use serde_json::Map; - use serde_json::Value; - - use super::*; - use crate::output::models; - use crate::output::models::ValidatorType; - - #[test] - fn test_schema_creation_from_builder() -> Result<()> { - let version = SchemaVersion::new(); - assert_eq!(version.major, models::SPEC_VERSION.0); - assert_eq!(version.minor, models::SPEC_VERSION.1); - Ok(()) - } - - #[test] - fn test_dut_creation_from_builder_with_defaults() -> Result<()> { - let dut = DutInfo::builder("1234").build(); - assert_eq!(dut.id, "1234"); - Ok(()) - } - - #[test] - fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { - let log = Log::builder("test") - .severity(models::LogSeverity::Info) - .build(); - - let artifact = log.to_artifact(ArtifactContext::TestRun); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { - let log = Log::builder("test") - .severity(models::LogSeverity::Info) - .build(); - - let artifact = log.to_artifact(ArtifactContext::TestStep); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { - let error = Error::builder("symptom") - .message("") - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .source("", 1) - .build(); - - let artifact = error.to_artifact(ArtifactContext::TestRun); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { - let error = Error::builder("symptom") - .message("") - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .source("", 1) - .build(); - - let artifact = error.to_artifact(ArtifactContext::TestStep); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let value = Value::from(50); - let measurement = Measurement::new(&name, value.clone()); - - let artifact = measurement.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name: name.to_string(), - unit: None, - value, - validators: None, - hardware_info_id: None, - subcomponent: None, - metadata: None, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let value = Value::from(50000); - let hardware_info = HardwareInfo::builder("id", "name").build(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - - let meta_key = "key"; - let meta_value = Value::from("value"); - let mut metadata = Map::new(); - metadata.insert(meta_key.to_string(), meta_value.clone()); - metadata.insert(meta_key.to_string(), meta_value.clone()); - - let subcomponent = Subcomponent::builder("name").build(); - - let unit = "RPM"; - let measurement = Measurement::builder(&name, value.clone()) - .hardware_info(&hardware_info) - .add_validator(&validator) - .add_validator(&validator) - .add_metadata(meta_key, meta_value.clone()) - .add_metadata(meta_key, meta_value.clone()) - .subcomponent(&subcomponent) - .unit(unit) - .build(); - - let artifact = measurement.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name, - unit: Some(unit.to_string()), - value, - validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(metadata), - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_start_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesStart::new(&name, &series_id); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_start_builder_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); - let hw_info = HardwareInfo::builder("id", "name").build(); - let subcomponent = Subcomponent::builder("name").build(); - let series = MeasurementSeriesStart::builder(&name, &series_id) - .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_validator(&validator) - .add_validator(&validator2) - .hardware_info(&hw_info) - .subcomponent(&subcomponent) - .build(); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name, - unit: Some("unit".to_string()), - series_id: series_id.to_string(), - validators: Some(vec![validator.to_spec(), validator2.to_spec()]), - hardware_info: Some(hw_info.to_spec()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(serde_json::Map::from_iter([ - ("key".to_string(), "value".into()), - ("key2".to_string(), "value2".into()) - ])), - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_end_to_artifact() -> Result<()> { - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesEnd::new(&series_id, 1); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { - series_id: series_id.to_string(), - total_count: 1, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_dut_builder() -> Result<()> { - let platform = PlatformInfo::builder("platform_info").build(); - let software = SoftwareInfo::builder("software_id", "name").build(); - let hardware = HardwareInfo::builder("hardware_id", "name").build(); - let dut = DutInfo::builder("1234") - .name("DUT") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_hardware_info(&hardware) - .add_hardware_info(&hardware) - .add_platform_info(&platform) - .add_platform_info(&platform) - .add_software_info(&software) - .add_software_info(&software) - .build(); - - let spec_dut = dut.to_spec(); - - assert_eq!(spec_dut.id, "1234"); - assert_eq!(spec_dut.name, Some("DUT".to_owned())); - - match spec_dut.metadata { - Some(m) => { - assert_eq!(m["key"], "value"); - assert_eq!(m["key2"], "value2"); - } - _ => bail!("metadata is empty"), - } - - match spec_dut.hardware_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "hardware_id"); - } - _ => bail!("hardware_infos is empty"), - }, - _ => bail!("hardware_infos is missing"), - } - - match spec_dut.software_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "software_id"); - } - _ => bail!("software_infos is empty"), - }, - _ => bail!("software_infos is missing"), - } - - match spec_dut.platform_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.info, "platform_info"); - } - _ => bail!("platform_infos is empty"), - }, - _ => bail!("platform_infos is missing"), - } - - Ok(()) - } - - #[test] - fn test_error() -> Result<()> { - let expected_run = serde_json::json!({ - "testRunArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file": "file.rs", "line": 1}, - "symptom": "symptom" - } - } - }); - let expected_step = serde_json::json!({ - "testStepArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file":"file.rs","line":1}, - "symptom":"symptom" - } - } - }); - - let software = SoftwareInfo::builder("software_id", "name").build(); - let error = ErrorBuilder::new("symptom") - .message("message") - .source("file.rs", 1) - .add_software_info(&software) - .add_software_info(&software) - .build(); - - let spec_error = error.to_artifact(ArtifactContext::TestRun); - let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_run); - - let spec_error = error.to_artifact(ArtifactContext::TestStep); - let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_step); - - Ok(()) - } - - #[test] - fn test_validator() -> Result<()> { - let validator = Validator::builder(ValidatorType::Equal, 30.into()) - .name("validator") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .build(); - - let spec_validator = validator.to_spec(); - - assert_eq!(spec_validator.name, Some("validator".to_owned())); - assert_eq!(spec_validator.value, 30); - assert_eq!(spec_validator.validator_type, ValidatorType::Equal); - - match spec_validator.metadata { - Some(m) => { - assert_eq!(m["key"], "value"); - assert_eq!(m["key2"], "value2"); - } - _ => bail!("metadata is none"), - } - - Ok(()) - } - - #[test] - fn test_hardware_info() -> Result<()> { - let info = HardwareInfo::builder("hardware_id", "hardware_name") - .version("version") - .revision("revision") - .location("location") - .serial_no("serial_no") - .part_no("part_no") - .manufacturer("manufacturer") - .manufacturer_part_no("manufacturer_part_no") - .odata_id("odata_id") - .computer_system("computer_system") - .manager("manager") - .build(); - - let spec_hwinfo = info.to_spec(); - - assert_eq!(spec_hwinfo.id, "hardware_id"); - assert_eq!(spec_hwinfo.name, "hardware_name"); - assert_eq!(spec_hwinfo.version, Some("version".to_owned())); - assert_eq!(spec_hwinfo.revision, Some("revision".to_owned())); - assert_eq!(spec_hwinfo.location, Some("location".to_owned())); - assert_eq!(spec_hwinfo.serial_no, Some("serial_no".to_owned())); - assert_eq!(spec_hwinfo.part_no, Some("part_no".to_owned())); - assert_eq!(spec_hwinfo.manufacturer, Some("manufacturer".to_owned())); - assert_eq!( - spec_hwinfo.manufacturer_part_no, - Some("manufacturer_part_no".to_owned()) - ); - assert_eq!(spec_hwinfo.odata_id, Some("odata_id".to_owned())); - assert_eq!( - spec_hwinfo.computer_system, - Some("computer_system".to_owned()) - ); - assert_eq!(spec_hwinfo.manager, Some("manager".to_owned())); - - Ok(()) - } - - #[test] - fn test_subcomponent() -> Result<()> { - let sub = Subcomponent::builder("sub_name") - .subcomponent_type(models::SubcomponentType::Asic) - .version("version") - .location("location") - .revision("revision") - .build(); - - let spec_subcomponent = sub.to_spec(); - - assert_eq!(spec_subcomponent.name, "sub_name"); - assert_eq!(spec_subcomponent.version, Some("version".to_owned())); - assert_eq!(spec_subcomponent.revision, Some("revision".to_owned())); - assert_eq!(spec_subcomponent.location, Some("location".to_owned())); - assert_eq!( - spec_subcomponent.subcomponent_type, - Some(models::SubcomponentType::Asic) - ); - - Ok(()) - } - - #[test] - fn test_platform_info() -> Result<()> { - let info = PlatformInfo::builder("info").build(); - - assert_eq!(info.to_spec().info, "info"); - Ok(()) - } - - #[test] - fn test_software_info() -> Result<()> { - let info = SoftwareInfo::builder("software_id", "name") - .version("version") - .revision("revision") - .software_type(models::SoftwareType::Application) - .computer_system("system") - .build(); - - let spec_swinfo = info.to_spec(); - - assert_eq!(spec_swinfo.id, "software_id"); - assert_eq!(spec_swinfo.name, "name"); - assert_eq!(spec_swinfo.version, Some("version".to_owned())); - assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); - assert_eq!( - spec_swinfo.software_type, - Some(models::SoftwareType::Application) - ); - assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); - - Ok(()) - } -} diff --git a/src/output/run.rs b/src/output/run.rs index b3d4a9f..15203df 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -12,12 +12,8 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::config; -use tv::emitters; -use tv::models; -use tv::objects; -use tv::state; use tv::step::TestStep; +use tv::{config, dut, emitters, error, log, models, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -35,7 +31,7 @@ pub struct TestRun { name: String, version: String, parameters: Map, - dut: objects::DutInfo, + dut: dut::DutInfo, command_line: String, metadata: Option>, state: Arc>, @@ -52,7 +48,7 @@ impl TestRun { /// let dut = DutInfo::builder("my_dut").build(); /// let builder = TestRun::builder("run_name", &dut, "1.0"); /// ``` - pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { + pub fn builder(name: &str, dut: &dut::DutInfo, version: &str) -> TestRunBuilder { TestRunBuilder::new(name, dut, version) } @@ -66,7 +62,7 @@ impl TestRun { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// ``` pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { - let dut = objects::DutInfo::new(dut_id); + let dut = dut::DutInfo::new(dut_id); TestRunBuilder::new(name, &dut, version).build() } @@ -88,7 +84,7 @@ impl TestRun { /// # }); /// ``` pub async fn start(self) -> Result { - let version = objects::SchemaVersion::new(); + let version = SchemaVersion::new(); self.state .lock() .await @@ -96,7 +92,7 @@ impl TestRun { .emit(&version.to_artifact()) .await?; - let mut builder = objects::TestRunStart::builder( + let mut builder = run::TestRunStart::builder( &self.name, &self.version, &self.command_line, @@ -165,7 +161,7 @@ impl TestRun { /// Builder for the [`TestRun`] object. pub struct TestRunBuilder { name: String, - dut: objects::DutInfo, + dut: dut::DutInfo, version: String, parameters: Map, command_line: String, @@ -174,7 +170,7 @@ pub struct TestRunBuilder { } impl TestRunBuilder { - pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + pub fn new(name: &str, dut: &dut::DutInfo, version: &str) -> Self { Self { name: name.to_string(), dut: dut.clone(), @@ -310,7 +306,7 @@ impl StartedTestRun { status: models::TestStatus, result: models::TestResult, ) -> Result<(), emitters::WriterError> { - let end = objects::TestRunEnd::builder() + let end = run::TestRunEnd::builder() .status(status) .result(result) .build(); @@ -348,12 +344,12 @@ impl StartedTestRun { severity: models::LogSeverity, msg: &str, ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); + let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&log.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -381,11 +377,11 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&log.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -409,11 +405,11 @@ impl StartedTestRun { /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); + let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -442,11 +438,11 @@ impl StartedTestRun { symptom: &str, msg: &str, ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); + let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -477,12 +473,12 @@ impl StartedTestRun { /// ``` pub async fn error_with_details( &self, - error: &objects::Error, + error: &error::Error, ) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -491,3 +487,189 @@ impl StartedTestRun { TestStep::new(name, self.run.state.clone()) } } + +pub struct TestRunStart { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: dut::DutInfo, +} + +impl TestRunStart { + pub fn builder( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &dut::DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + name: self.name.clone(), + version: self.version.clone(), + command_line: self.command_line.clone(), + parameters: self.parameters.clone(), + metadata: self.metadata.clone(), + dut_info: self.dut_info.to_spec(), + }), + }) + } +} + +pub struct TestRunStartBuilder { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: dut::DutInfo, +} + +impl TestRunStartBuilder { + pub fn new( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &dut::DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder { + name: name.to_string(), + version: version.to_string(), + command_line: command_line.to_string(), + parameters: parameters.clone(), + metadata: None, + dut_info: dut_info.clone(), + } + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRunStart { + TestRunStart { + name: self.name, + version: self.version, + command_line: self.command_line, + parameters: self.parameters, + metadata: self.metadata, + dut_info: self.dut_info, + } + } +} + +pub struct TestRunEnd { + status: models::TestStatus, + result: models::TestResult, +} + +impl TestRunEnd { + pub fn builder() -> TestRunEndBuilder { + TestRunEndBuilder::new() + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + status: self.status.clone(), + result: self.result.clone(), + }), + }) + } +} + +#[derive(Debug)] +pub struct TestRunEndBuilder { + status: models::TestStatus, + result: models::TestResult, +} + +#[allow(clippy::new_without_default)] +impl TestRunEndBuilder { + pub fn new() -> TestRunEndBuilder { + TestRunEndBuilder { + status: models::TestStatus::Complete, + result: models::TestResult::Pass, + } + } + pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { + self.status = value; + self + } + + pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { + self.result = value; + self + } + + pub fn build(self) -> TestRunEnd { + TestRunEnd { + status: self.status, + result: self.result, + } + } +} + +// TODO: move this away from here +pub enum ArtifactContext { + TestRun, + TestStep, +} + +// TODO: this likely will go into the emitter since it's not the run's job to emit the schema version +pub struct SchemaVersion { + major: i8, + minor: i8, +} + +#[allow(clippy::new_without_default)] +impl SchemaVersion { + pub fn new() -> SchemaVersion { + SchemaVersion { + major: models::SPEC_VERSION.0, + minor: models::SPEC_VERSION.1, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { + major: self.major, + minor: self.minor, + }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::output as tv; + use tv::models; + + #[test] + fn test_schema_creation_from_builder() -> Result<()> { + let version = SchemaVersion::new(); + assert_eq!(version.major, models::SPEC_VERSION.0); + assert_eq!(version.minor, models::SPEC_VERSION.1); + Ok(()) + } +} diff --git a/src/output/step.rs b/src/output/step.rs index 59b6ddb..86659a4 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -10,8 +10,8 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; -use tv::measurement_series::MeasurementSeries; -use tv::{emitters, models, objects, state}; +use tv::measurement::MeasurementSeries; +use tv::{emitters, error, log, measurement, models, run, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -46,7 +46,7 @@ impl TestStep { /// # }); /// ``` pub async fn start(self) -> Result { - let start = objects::TestStepStart::new(&self.name); + let start = step::TestStepStart::new(&self.name); self.state .lock() .await @@ -124,7 +124,7 @@ impl StartedTestStep { /// # }); /// ``` pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { - let end = objects::TestStepEnd::new(status); + let end = step::TestStepEnd::new(status); self.step .state .lock() @@ -181,13 +181,13 @@ impl StartedTestStep { severity: models::LogSeverity, msg: &str, ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); + let log = log::Log::builder(msg).severity(severity).build(); self.step .state .lock() .await .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&log.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -217,13 +217,13 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { self.step .state .lock() .await .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&log.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -267,13 +267,13 @@ impl StartedTestStep { /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); + let error = error::Error::builder(symptom).build(); self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -322,13 +322,13 @@ impl StartedTestStep { symptom: &str, msg: &str, ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); + let error = error::Error::builder(symptom).message(msg).build(); self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -361,14 +361,14 @@ impl StartedTestStep { /// ``` pub async fn error_with_details( &self, - error: &objects::Error, + error: &error::Error, ) -> Result<(), emitters::WriterError> { self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -397,7 +397,7 @@ impl StartedTestStep { name: &str, value: Value, ) -> Result<(), emitters::WriterError> { - let measurement = objects::Measurement::new(name, value); + let measurement = measurement::Measurement::new(name, value); self.step .state .lock() @@ -437,7 +437,7 @@ impl StartedTestStep { /// ``` pub async fn add_measurement_with_details( &self, - measurement: &objects::Measurement, + measurement: &measurement::Measurement, ) -> Result<(), emitters::WriterError> { self.step .state @@ -500,8 +500,51 @@ impl StartedTestStep { /// ``` pub fn measurement_series_with_details( &self, - start: objects::MeasurementSeriesStart, + start: measurement::MeasurementSeriesStart, ) -> MeasurementSeries { MeasurementSeries::new_with_details(start, self.step.state.clone()) } } + +pub struct TestStepStart { + name: String, +} + +impl TestStepStart { + pub fn new(name: &str) -> TestStepStart { + TestStepStart { + name: name.to_string(), + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepStart( + models::TestStepStartSpec { + name: self.name.clone(), + }, + ), + }) + } +} + +pub struct TestStepEnd { + status: models::TestStatus, +} + +impl TestStepEnd { + pub fn new(status: models::TestStatus) -> TestStepEnd { + TestStepEnd { status } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { + status: self.status.clone(), + }), + }) + } +} + +#[cfg(test)] +mod tests {} From 9a2cc6ff0db2d0843e8edfc7430cf491ecba6f49 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 19:33:50 +0100 Subject: [PATCH 3/5] remove the leaky ArtifactContext - previously all log and error outputs needed to know where theyre being called from; this is an antipattern and leaks impl detail - move this context to the proper spaces (in StartedTestRun, StartedTestStep) - this commit changes semantics of the `to_artifact` a bit, but this will be cleared in the next commits which refactor the emitter Signed-off-by: mimir-d --- src/output/error.rs | 158 ++++++++++++++++++-------------------------- src/output/log.rs | 55 +++++---------- src/output/run.rs | 36 +++++++--- src/output/step.rs | 82 ++++++++++++++--------- 4 files changed, 159 insertions(+), 172 deletions(-) diff --git a/src/output/error.rs b/src/output/error.rs index d61e9a0..a940c10 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -5,7 +5,7 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{dut, models, run::ArtifactContext}; +use tv::{dut, models}; pub struct Error { symptom: String, @@ -19,28 +19,12 @@ impl Error { ErrorBuilder::new(symptom) } - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } + pub fn to_artifact(&self) -> models::ErrorSpec { + models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), } } } @@ -112,17 +96,15 @@ mod tests { .source("", 1) .build(); - let artifact = error.to_artifact(ArtifactContext::TestRun); + let artifact = error.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) + models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } ); Ok(()) @@ -136,17 +118,15 @@ mod tests { .source("", 1) .build(); - let artifact = error.to_artifact(ArtifactContext::TestStep); + let artifact = error.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) + models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } ); Ok(()) @@ -155,60 +135,52 @@ mod tests { #[test] fn test_error() -> Result<()> { let expected_run = serde_json::json!({ - "testRunArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file": "file.rs", "line": 1}, - "symptom": "symptom" + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null } - } + ], + "sourceLocation": {"file": "file.rs", "line": 1}, + "symptom": "symptom" }); let expected_step = serde_json::json!({ - "testStepArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file":"file.rs","line":1}, - "symptom":"symptom" + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null } - } + ], + "sourceLocation": {"file":"file.rs","line":1}, + "symptom":"symptom" }); let software = dut::SoftwareInfo::builder("software_id", "name").build(); @@ -219,11 +191,11 @@ mod tests { .add_software_info(&software) .build(); - let spec_error = error.to_artifact(ArtifactContext::TestRun); + let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_run); - let spec_error = error.to_artifact(ArtifactContext::TestStep); + let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_step); diff --git a/src/output/log.rs b/src/output/log.rs index f76af11..8228b58 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -5,7 +5,7 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{models, run::ArtifactContext}; +use tv::models; pub struct Log { severity: models::LogSeverity, @@ -18,26 +18,11 @@ impl Log { LogBuilder::new(message) } - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } + pub fn to_artifact(&self) -> models::LogSpec { + models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), } } } @@ -92,16 +77,14 @@ mod tests { .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(ArtifactContext::TestRun); + let artifact = log.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) + models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }, ); Ok(()) @@ -113,16 +96,14 @@ mod tests { .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(ArtifactContext::TestStep); + let artifact = log.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) + models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + } ); Ok(()) diff --git a/src/output/run.rs b/src/output/run.rs index 15203df..2e72bda 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -348,9 +348,13 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + }; emitter - .emit(&log.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -380,9 +384,13 @@ impl StartedTestRun { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + }; emitter - .emit(&log.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -408,9 +416,13 @@ impl StartedTestRun { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -441,9 +453,13 @@ impl StartedTestRun { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -477,9 +493,13 @@ impl StartedTestRun { ) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -628,12 +648,6 @@ impl TestRunEndBuilder { } } -// TODO: move this away from here -pub enum ArtifactContext { - TestRun, - TestStep, -} - // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version pub struct SchemaVersion { major: i8, diff --git a/src/output/step.rs b/src/output/step.rs index 86659a4..749a0a8 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -11,7 +11,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::measurement::MeasurementSeries; -use tv::{emitters, error, log, measurement, models, run, state, step}; +use tv::{emitters, error, log, measurement, models, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -182,13 +182,17 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -218,13 +222,17 @@ impl StartedTestStep { /// # }); /// ``` pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -268,13 +276,17 @@ impl StartedTestStep { /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = error::Error::builder(symptom).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -323,13 +335,17 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -363,13 +379,17 @@ impl StartedTestStep { &self, error: &error::Error, ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } From 49594846fbfb1005c327b9894655c991744ab194 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 19:56:32 +0100 Subject: [PATCH 4/5] some renames; better formatting in spec models Signed-off-by: mimir-d --- src/output/config.rs | 18 ++--- src/output/{emitters.rs => emitter.rs} | 12 ++-- src/output/measurement.rs | 40 +++++------ src/output/mod.rs | 4 +- src/output/models.rs | 99 ++++++++++++++++++++++++-- src/output/run.rs | 54 +++++++------- src/output/state.rs | 6 +- src/output/step.rs | 48 +++++-------- 8 files changed, 179 insertions(+), 102 deletions(-) rename src/output/{emitters.rs => emitter.rs} (93%) diff --git a/src/output/config.rs b/src/output/config.rs index f97baaa..d5c27b8 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -8,12 +8,12 @@ use std::path::Path; use std::sync::Arc; use tokio::sync::Mutex; -use crate::output::emitters; +use crate::output::emitter; /// The configuration repository for the TestRun. pub struct Config { pub(crate) timezone: chrono_tz::Tz, - pub(crate) writer: emitters::WriterType, + pub(crate) writer: emitter::WriterType, } impl Config { @@ -33,14 +33,14 @@ impl Config { /// The builder for the [`Config`] object. pub struct ConfigBuilder { timezone: Option, - writer: Option, + writer: Option, } impl ConfigBuilder { fn new() -> Self { Self { timezone: None, - writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + writer: Some(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } @@ -50,7 +50,7 @@ impl ConfigBuilder { } pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { - self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( + self.writer = Some(emitter::WriterType::Buffer(emitter::BufferWriter::new( buffer, ))); self @@ -59,9 +59,9 @@ impl ConfigBuilder { pub async fn with_file_output>( mut self, path: P, - ) -> Result { - self.writer = Some(emitters::WriterType::File( - emitters::FileWriter::new(path).await?, + ) -> Result { + self.writer = Some(emitter::WriterType::File( + emitter::FileWriter::new(path).await?, )); Ok(self) } @@ -71,7 +71,7 @@ impl ConfigBuilder { timezone: self.timezone.unwrap_or(chrono_tz::UTC), writer: self .writer - .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + .unwrap_or(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } } diff --git a/src/output/emitters.rs b/src/output/emitter.rs similarity index 93% rename from src/output/emitters.rs rename to src/output/emitter.rs index d818b65..1a96bad 100644 --- a/src/output/emitters.rs +++ b/src/output/emitter.rs @@ -98,13 +98,13 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &models::OutputArtifactDescendant) -> serde_json::Value { + fn serialize_artifact(&self, object: &models::RootArtifactSpec) -> serde_json::Value { let now = chrono::Local::now(); let now_tz = now.with_timezone(&self.timezone); - let out_artifact = models::OutputArtifactSpec { - descendant: object.clone(), - now: now_tz, - sequence_number: self.next_sequence_no(), + let out_artifact = models::RootSpec { + artifact: object.clone(), + timestamp: now_tz, + seqno: self.next_sequence_no(), }; serde_json::json!(out_artifact) } @@ -114,7 +114,7 @@ impl JsonEmitter { self.sequence_no.load(atomic::Ordering::SeqCst) } - pub async fn emit(&self, object: &models::OutputArtifactDescendant) -> Result<(), WriterError> { + pub async fn emit(&self, object: &models::RootArtifactSpec) -> Result<(), WriterError> { let serialized = self.serialize_artifact(object); match self.writer { WriterType::File(ref file) => file.write(&serialized.to_string()).await?, diff --git a/src/output/measurement.rs b/src/output/measurement.rs index c641d8b..5badd7f 100644 --- a/src/output/measurement.rs +++ b/src/output/measurement.rs @@ -14,7 +14,7 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::{dut, emitters, models, state}; +use tv::{dut, emitter, models, state}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -76,7 +76,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { + pub async fn start(&self) -> Result<(), emitter::WriterError> { self.state .lock() .await @@ -106,7 +106,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { + pub async fn end(&self) -> Result<(), emitter::WriterError> { let end = MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); self.state @@ -138,7 +138,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + pub async fn add_measurement(&self, value: Value) -> Result<(), emitter::WriterError> { let element = MeasurementSeriesElement::new( self.current_sequence_no().await, value, @@ -180,7 +180,7 @@ impl MeasurementSeries { &self, value: Value, metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let element = MeasurementSeriesElement::new( self.current_sequence_no().await, value, @@ -227,9 +227,9 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitter::WriterError> where - R: Future>, + R: Future>, F: std::ops::FnOnce(&'a MeasurementSeries) -> R, { self.start().await?; @@ -405,8 +405,8 @@ impl Measurement { /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { name: self.name.clone(), unit: self.unit.clone(), @@ -633,8 +633,8 @@ impl MeasurementSeriesStart { MeasurementSeriesStartBuilder::new(name, series_id) } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name: self.name.clone(), @@ -758,8 +758,8 @@ impl MeasurementSeriesEnd { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( models::MeasurementSeriesEndSpec { series_id: self.series_id.clone(), @@ -794,8 +794,8 @@ impl MeasurementSeriesElement { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( models::MeasurementSeriesElementSpec { index: self.index, @@ -827,7 +827,7 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement( models::MeasurementSpec { name: name.to_string(), @@ -874,7 +874,7 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement( models::MeasurementSpec { name, @@ -901,7 +901,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name: name.to_string(), @@ -940,7 +940,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name, @@ -969,7 +969,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( models::MeasurementSeriesEndSpec { series_id: series_id.to_string(), diff --git a/src/output/mod.rs b/src/output/mod.rs index d296c57..6326631 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -6,7 +6,7 @@ mod config; mod dut; -mod emitters; +mod emitter; mod error; mod log; mod macros; @@ -18,7 +18,7 @@ mod step; pub use config::*; pub use dut::*; -pub use emitters::*; +pub use emitter::*; pub use error::*; pub use log::*; pub use measurement::*; diff --git a/src/output/models.rs b/src/output/models.rs index 48a2493..db187d1 100644 --- a/src/output/models.rs +++ b/src/output/models.rs @@ -42,20 +42,25 @@ mod rfc3339_format { pub enum TestRunArtifactDescendant { #[serde(rename = "testRunStart")] TestRunStart(TestRunStartSpec), + #[serde(rename = "testRunEnd")] TestRunEnd(TestRunEndSpec), + #[serde(rename = "log")] Log(LogSpec), + #[serde(rename = "error")] Error(ErrorSpec), } #[derive(Debug, Serialize, PartialEq, Clone)] -pub enum OutputArtifactDescendant { +pub enum RootArtifactSpec { #[serde(rename = "schemaVersion")] SchemaVersion(SchemaVersionSpec), + #[serde(rename = "testRunArtifact")] TestRunArtifact(TestRunArtifactSpec), + #[serde(rename = "testStepArtifact")] TestStepArtifact(TestStepArtifactSpec), } @@ -65,24 +70,34 @@ pub enum OutputArtifactDescendant { pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] TestStepStart(TestStepStartSpec), + #[serde(rename = "testStepEnd")] TestStepEnd(TestStepEndSpec), + #[serde(rename = "measurement")] Measurement(MeasurementSpec), + #[serde(rename = "measurementSeriesStart")] MeasurementSeriesStart(MeasurementSeriesStartSpec), + #[serde(rename = "measurementSeriesEnd")] MeasurementSeriesEnd(MeasurementSeriesEndSpec), + #[serde(rename = "measurementSeriesElement")] MeasurementSeriesElement(MeasurementSeriesElementSpec), + #[serde(rename = "diagnosis")] Diagnosis(DiagnosisSpec), + #[serde(rename = "log")] Log(LogSpec), + #[serde(rename = "error")] Error(ErrorSpec), + #[serde(rename = "file")] File(FileSpec), + #[serde(rename = "extension")] Extension(ExtensionSpec), } @@ -127,6 +142,7 @@ pub enum SubcomponentType { Connector, } +// TODO: this should be better typed #[derive(Debug, Serialize, PartialEq, Clone)] pub enum ExtensionContentType { #[serde(rename = "float")] @@ -218,15 +234,17 @@ pub enum SoftwareType { } #[derive(Debug, Serialize, Clone)] -pub struct OutputArtifactSpec { +pub struct RootSpec { #[serde(flatten)] - pub descendant: OutputArtifactDescendant, + pub artifact: RootArtifactSpec, + // TODO : manage different timezones #[serde(rename = "timestamp")] #[serde(with = "rfc3339_format")] - pub now: DateTime, + pub timestamp: DateTime, + #[serde(rename = "sequenceNumber")] - pub sequence_number: u64, + pub seqno: u64, } /// Low-level model for the `schemaVersion` spec object. @@ -239,6 +257,7 @@ pub struct OutputArtifactSpec { pub struct SchemaVersionSpec { #[serde(rename = "major")] pub major: i8, + #[serde(rename = "minor")] pub minor: i8, } @@ -251,7 +270,7 @@ pub struct SchemaVersionSpec { #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifactSpec { #[serde(flatten)] - pub descendant: TestRunArtifactDescendant, + pub artifact: TestRunArtifactDescendant, } /// Low-level model for the `testRunStart` spec object. @@ -264,14 +283,19 @@ pub struct TestRunArtifactSpec { pub struct TestRunStartSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: String, + #[serde(rename = "commandLine")] pub command_line: String, + #[serde(rename = "parameters")] pub parameters: Map, + #[serde(rename = "dutInfo")] pub dut_info: DutInfoSpec, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -286,14 +310,19 @@ pub struct TestRunStartSpec { pub struct DutInfoSpec { #[serde(rename = "dutInfoId")] pub id: String, + #[serde(rename = "name")] pub name: Option, + #[serde(rename = "platformInfos")] pub platform_infos: Option>, + #[serde(rename = "softwareInfos")] pub software_infos: Option>, + #[serde(rename = "hardwareInfos")] pub hardware_infos: Option>, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -320,14 +349,19 @@ pub struct PlatformInfoSpec { pub struct SoftwareInfoSpec { #[serde(rename = "softwareInfoId")] pub id: String, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, + #[serde(rename = "softwareType")] pub software_type: Option, + #[serde(rename = "computerSystem")] pub computer_system: Option, } @@ -342,26 +376,37 @@ pub struct SoftwareInfoSpec { pub struct HardwareInfoSpec { #[serde(rename = "hardwareInfoId")] pub id: String, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, + #[serde(rename = "location")] pub location: Option, + #[serde(rename = "serialNumber")] pub serial_no: Option, + #[serde(rename = "partNumber")] pub part_no: Option, + #[serde(rename = "manufacturer")] pub manufacturer: Option, + #[serde(rename = "manufacturerPartNumber")] pub manufacturer_part_no: Option, + #[serde(rename = "odataId")] pub odata_id: Option, + #[serde(rename = "computerSystem")] pub computer_system: Option, + #[serde(rename = "manager")] pub manager: Option, } @@ -376,6 +421,7 @@ pub struct HardwareInfoSpec { pub struct TestRunEndSpec { #[serde(rename = "status")] pub status: TestStatus, + #[serde(rename = "result")] pub result: TestResult, } @@ -391,11 +437,14 @@ pub struct TestRunEndSpec { pub struct ErrorSpec { #[serde(rename = "symptom")] pub symptom: String, + #[serde(rename = "message")] pub message: Option, + // TODO: support this field during serialization to print only the id of SoftwareInfo struct #[serde(rename = "softwareInfoIds")] pub software_infos: Option>, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -410,8 +459,10 @@ pub struct ErrorSpec { pub struct LogSpec { #[serde(rename = "severity")] pub severity: LogSeverity, + #[serde(rename = "message")] pub message: String, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -426,6 +477,7 @@ pub struct LogSpec { pub struct SourceLocationSpec { #[serde(rename = "file")] pub file: String, + #[serde(rename = "line")] pub line: i32, } @@ -475,16 +527,22 @@ pub struct TestStepEndSpec { pub struct MeasurementSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "value")] pub value: Value, + #[serde(rename = "unit")] pub unit: Option, + #[serde(rename = "validators")] pub validators: Option>, + #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, + #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -499,10 +557,13 @@ pub struct MeasurementSpec { pub struct ValidatorSpec { #[serde(rename = "name")] pub name: Option, + #[serde(rename = "type")] pub validator_type: ValidatorType, + #[serde(rename = "value")] pub value: Value, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -517,12 +578,16 @@ pub struct ValidatorSpec { pub struct SubcomponentSpec { #[serde(rename = "type")] pub subcomponent_type: Option, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "location")] pub location: Option, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, } @@ -537,16 +602,22 @@ pub struct SubcomponentSpec { pub struct MeasurementSeriesStartSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "unit")] pub unit: Option, + #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "validators")] pub validators: Option>, + #[serde(rename = "hardwareInfoId")] pub hardware_info: Option, + #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -561,6 +632,7 @@ pub struct MeasurementSeriesStartSpec { pub struct MeasurementSeriesEndSpec { #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "totalCount")] pub total_count: u64, } @@ -575,12 +647,16 @@ pub struct MeasurementSeriesEndSpec { pub struct MeasurementSeriesElementSpec { #[serde(rename = "index")] pub index: u64, + #[serde(rename = "value")] pub value: Value, + #[serde(with = "rfc3339_format")] pub timestamp: DateTime, + #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -595,14 +671,19 @@ pub struct MeasurementSeriesElementSpec { pub struct DiagnosisSpec { #[serde(rename = "verdict")] pub verdict: String, + #[serde(rename = "type")] pub diagnosis_type: DiagnosisType, + #[serde(rename = "message")] pub message: Option, + #[serde(rename = "validators")] pub hardware_info: Option, + #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -617,14 +698,19 @@ pub struct DiagnosisSpec { pub struct FileSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "uri")] pub uri: String, + #[serde(rename = "isSnapshot")] pub is_snapshot: bool, + #[serde(rename = "description")] pub description: Option, + #[serde(rename = "contentType")] pub content_type: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -639,6 +725,7 @@ pub struct FileSpec { pub struct ExtensionSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "content")] pub content: ExtensionContentType, } diff --git a/src/output/run.rs b/src/output/run.rs index 2e72bda..dce57f5 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -13,7 +13,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::step::TestStep; -use tv::{config, dut, emitters, error, log, models, run, state}; +use tv::{config, dut, emitter, error, log, models, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -83,7 +83,7 @@ impl TestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let version = SchemaVersion::new(); self.state .lock() @@ -263,7 +263,7 @@ impl TestRunBuilder { pub fn build(self) -> TestRun { let config = self.config.unwrap_or(config::Config::builder().build()); - let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let emitter = emitter::JsonEmitter::new(config.timezone, config.writer); let state = state::TestState::new(emitter); TestRun { name: self.name, @@ -305,7 +305,7 @@ impl StartedTestRun { &self, status: models::TestStatus, result: models::TestResult, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let end = run::TestRunEnd::builder() .status(status) .result(result) @@ -343,16 +343,16 @@ impl StartedTestRun { &self, severity: models::LogSeverity, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -381,14 +381,14 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -412,15 +412,15 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -449,15 +449,15 @@ impl StartedTestRun { &self, symptom: &str, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -490,14 +490,14 @@ impl StartedTestRun { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -528,9 +528,9 @@ impl TestRunStart { TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { + artifact: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { name: self.name.clone(), version: self.version.clone(), command_line: self.command_line.clone(), @@ -606,9 +606,9 @@ impl TestRunEnd { TestRunEndBuilder::new() } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { + artifact: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { status: self.status.clone(), result: self.result.clone(), }), @@ -663,8 +663,8 @@ impl SchemaVersion { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::SchemaVersion(models::SchemaVersionSpec { major: self.major, minor: self.minor, }) diff --git a/src/output/state.rs b/src/output/state.rs index 63164f8..df1fe96 100644 --- a/src/output/state.rs +++ b/src/output/state.rs @@ -4,15 +4,15 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output::emitters; +use crate::output::emitter; // TODO: will prob need some redesign pub struct TestState { - pub emitter: emitters::JsonEmitter, + pub emitter: emitter::JsonEmitter, } impl TestState { - pub fn new(emitter: emitters::JsonEmitter) -> TestState { + pub fn new(emitter: emitter::JsonEmitter) -> TestState { TestState { emitter } } } diff --git a/src/output/step.rs b/src/output/step.rs index 749a0a8..de8da4d 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -11,7 +11,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::measurement::MeasurementSeries; -use tv::{emitters, error, log, measurement, models, state, step}; +use tv::{emitter, error, log, measurement, models, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -45,7 +45,7 @@ impl TestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let start = step::TestStepStart::new(&self.name); self.state .lock() @@ -123,7 +123,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { + pub async fn end(&self, status: models::TestStatus) -> Result<(), emitter::WriterError> { let end = step::TestStepEnd::new(status); self.step .state @@ -180,7 +180,7 @@ impl StartedTestStep { &self, severity: models::LogSeverity, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.step.state.lock().await.emitter; @@ -188,9 +188,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -221,16 +219,14 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; let artifact = models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -274,7 +270,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); let emitter = &self.step.state.lock().await.emitter; @@ -282,9 +278,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -333,7 +327,7 @@ impl StartedTestStep { &self, symptom: &str, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.step.state.lock().await.emitter; @@ -341,9 +335,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -378,16 +370,14 @@ impl StartedTestStep { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; let artifact = models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -416,7 +406,7 @@ impl StartedTestStep { &self, name: &str, value: Value, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let measurement = measurement::Measurement::new(name, value); self.step .state @@ -458,7 +448,7 @@ impl StartedTestStep { pub async fn add_measurement_with_details( &self, measurement: &measurement::Measurement, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { self.step .state .lock() @@ -537,8 +527,8 @@ impl TestStepStart { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::TestStepStart( models::TestStepStartSpec { name: self.name.clone(), @@ -557,8 +547,8 @@ impl TestStepEnd { TestStepEnd { status } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { status: self.status.clone(), }), From 4987393fa0a8c91ef06064ee99bddb7cdedf7490 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 20:09:24 +0100 Subject: [PATCH 5/5] move spec to a different module path - reasoning: this spec file will be used in multiple modules (serialized in ::output but deserialize in possibly ::parse) - remove the `Spec` suffix from the objects now, since that's clear from the module name Signed-off-by: mimir-d --- src/lib.rs | 1 + src/output/dut.rs | 45 ++++++----- src/output/emitter.rs | 20 ++--- src/output/error.rs | 24 +++--- src/output/log.rs | 32 ++++---- src/output/measurement.rs | 116 ++++++++++++++-------------- src/output/mod.rs | 11 ++- src/output/run.rs | 86 ++++++++++----------- src/output/step.rs | 59 +++++++-------- src/{output/models.rs => spec.rs} | 122 +++++++++++++++--------------- 10 files changed, 256 insertions(+), 260 deletions(-) rename src/{output/models.rs => spec.rs} (91%) diff --git a/src/lib.rs b/src/lib.rs index e01580c..ee95e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ // https://opensource.org/licenses/MIT. pub mod output; +mod spec; diff --git a/src/output/dut.rs b/src/output/dut.rs index 89679f2..b4d6c20 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -4,8 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output as tv; -use tv::models; +use crate::spec; #[derive(Default, Debug, Clone, PartialEq)] pub struct DutInfo { @@ -26,8 +25,8 @@ impl DutInfo { DutInfoBuilder::new(id).build() } - pub(crate) fn to_spec(&self) -> models::DutInfoSpec { - models::DutInfoSpec { + pub(crate) fn to_spec(&self) -> spec::DutInfo { + spec::DutInfo { id: self.id.clone(), name: self.name.clone(), platform_infos: self @@ -153,8 +152,8 @@ impl HardwareInfo { HardwareInfoBuilder::new(id, name) } - pub fn to_spec(&self) -> models::HardwareInfoSpec { - models::HardwareInfoSpec { + pub fn to_spec(&self) -> spec::HardwareInfo { + spec::HardwareInfo { id: self.id.clone(), name: self.name.clone(), version: self.version.clone(), @@ -269,7 +268,7 @@ impl HardwareInfoBuilder { #[derive(Debug, Clone)] pub struct Subcomponent { - subcomponent_type: Option, + subcomponent_type: Option, name: String, location: Option, version: Option, @@ -280,8 +279,8 @@ impl Subcomponent { pub fn builder(name: &str) -> SubcomponentBuilder { SubcomponentBuilder::new(name) } - pub fn to_spec(&self) -> models::SubcomponentSpec { - models::SubcomponentSpec { + pub fn to_spec(&self) -> spec::Subcomponent { + spec::Subcomponent { subcomponent_type: self.subcomponent_type.clone(), name: self.name.clone(), location: self.location.clone(), @@ -293,7 +292,7 @@ impl Subcomponent { #[derive(Debug)] pub struct SubcomponentBuilder { - subcomponent_type: Option, + subcomponent_type: Option, name: String, location: Option, version: Option, @@ -310,7 +309,7 @@ impl SubcomponentBuilder { revision: None, } } - pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { + pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> SubcomponentBuilder { self.subcomponent_type = Some(value); self } @@ -348,8 +347,8 @@ impl PlatformInfo { PlatformInfoBuilder::new(info) } - pub fn to_spec(&self) -> models::PlatformInfoSpec { - models::PlatformInfoSpec { + pub fn to_spec(&self) -> spec::PlatformInfo { + spec::PlatformInfo { info: self.info.clone(), } } @@ -378,7 +377,7 @@ pub struct SoftwareInfo { name: String, version: Option, revision: Option, - software_type: Option, + software_type: Option, computer_system: Option, } @@ -387,8 +386,8 @@ impl SoftwareInfo { SoftwareInfoBuilder::new(id, name) } - pub fn to_spec(&self) -> models::SoftwareInfoSpec { - models::SoftwareInfoSpec { + pub fn to_spec(&self) -> spec::SoftwareInfo { + spec::SoftwareInfo { id: self.id.clone(), name: self.name.clone(), version: self.version.clone(), @@ -405,7 +404,7 @@ pub struct SoftwareInfoBuilder { name: String, version: Option, revision: Option, - software_type: Option, + software_type: Option, computer_system: Option, } @@ -428,7 +427,7 @@ impl SoftwareInfoBuilder { self.revision = Some(value.to_string()); self } - pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { + pub fn software_type(mut self, value: spec::SoftwareType) -> SoftwareInfoBuilder { self.software_type = Some(value); self } @@ -452,7 +451,7 @@ impl SoftwareInfoBuilder { #[cfg(test)] mod tests { use super::*; - use crate::output::models; + use crate::spec; use anyhow::{bail, Result}; #[test] @@ -569,7 +568,7 @@ mod tests { let info = SoftwareInfo::builder("software_id", "name") .version("version") .revision("revision") - .software_type(models::SoftwareType::Application) + .software_type(spec::SoftwareType::Application) .computer_system("system") .build(); @@ -581,7 +580,7 @@ mod tests { assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); assert_eq!( spec_swinfo.software_type, - Some(models::SoftwareType::Application) + Some(spec::SoftwareType::Application) ); assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); @@ -599,7 +598,7 @@ mod tests { #[test] fn test_subcomponent() -> Result<()> { let sub = Subcomponent::builder("sub_name") - .subcomponent_type(models::SubcomponentType::Asic) + .subcomponent_type(spec::SubcomponentType::Asic) .version("version") .location("location") .revision("revision") @@ -613,7 +612,7 @@ mod tests { assert_eq!(spec_subcomponent.location, Some("location".to_owned())); assert_eq!( spec_subcomponent.subcomponent_type, - Some(models::SubcomponentType::Asic) + Some(spec::SubcomponentType::Asic) ); Ok(()) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 1a96bad..e26acd2 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -16,7 +16,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; -use crate::output::models; +use crate::spec; #[derive(Debug, thiserror::Error, derive_more::Display)] #[non_exhaustive] @@ -98,10 +98,10 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &models::RootArtifactSpec) -> serde_json::Value { + fn serialize_artifact(&self, object: &spec::RootArtifact) -> serde_json::Value { let now = chrono::Local::now(); let now_tz = now.with_timezone(&self.timezone); - let out_artifact = models::RootSpec { + let out_artifact = spec::Root { artifact: object.clone(), timestamp: now_tz, seqno: self.next_sequence_no(), @@ -114,7 +114,7 @@ impl JsonEmitter { self.sequence_no.load(atomic::Ordering::SeqCst) } - pub async fn emit(&self, object: &models::RootArtifactSpec) -> Result<(), WriterError> { + pub async fn emit(&self, object: &spec::RootArtifact) -> Result<(), WriterError> { let serialized = self.serialize_artifact(object); match self.writer { WriterType::File(ref file) => file.write(&serialized.to_string()).await?, @@ -139,8 +139,8 @@ mod tests { async fn test_emit_using_buffer_writer() -> Result<()> { let expected = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 1 }); @@ -164,15 +164,15 @@ mod tests { async fn test_sequence_number_increments_at_each_call() -> Result<()> { let expected_1 = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 1 }); let expected_2 = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 2 }); diff --git a/src/output/error.rs b/src/output/error.rs index a940c10..22b1d17 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -5,13 +5,14 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{dut, models}; +use crate::spec; +use tv::dut; pub struct Error { symptom: String, message: Option, - software_infos: Option>, - source_location: Option, + software_infos: Option>, + source_location: Option, } impl Error { @@ -19,8 +20,8 @@ impl Error { ErrorBuilder::new(symptom) } - pub fn to_artifact(&self) -> models::ErrorSpec { - models::ErrorSpec { + pub fn to_artifact(&self) -> spec::Error { + spec::Error { symptom: self.symptom.clone(), message: self.message.clone(), software_infos: self.software_infos.clone(), @@ -33,8 +34,8 @@ impl Error { pub struct ErrorBuilder { symptom: String, message: Option, - software_infos: Option>, - source_location: Option, + software_infos: Option>, + source_location: Option, } impl ErrorBuilder { @@ -51,7 +52,7 @@ impl ErrorBuilder { self } pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { - self.source_location = Some(models::SourceLocationSpec { + self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, }); @@ -86,7 +87,8 @@ mod tests { use super::*; use crate::output as tv; - use tv::{dut, models}; + use crate::spec; + use tv::dut; #[test] fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { @@ -99,7 +101,7 @@ mod tests { let artifact = error.to_artifact(); assert_eq!( artifact, - models::ErrorSpec { + spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), software_infos: error.software_infos.clone(), @@ -121,7 +123,7 @@ mod tests { let artifact = error.to_artifact(); assert_eq!( artifact, - models::ErrorSpec { + spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), software_infos: error.software_infos.clone(), diff --git a/src/output/log.rs b/src/output/log.rs index 8228b58..baf8635 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -4,13 +4,12 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output as tv; -use tv::models; +use crate::spec; pub struct Log { - severity: models::LogSeverity, + severity: spec::LogSeverity, message: String, - source_location: Option, + source_location: Option, } impl Log { @@ -18,8 +17,8 @@ impl Log { LogBuilder::new(message) } - pub fn to_artifact(&self) -> models::LogSpec { - models::LogSpec { + pub fn to_artifact(&self) -> spec::Log { + spec::Log { severity: self.severity.clone(), message: self.message.clone(), source_location: self.source_location.clone(), @@ -29,25 +28,25 @@ impl Log { #[derive(Debug)] pub struct LogBuilder { - severity: models::LogSeverity, + severity: spec::LogSeverity, message: String, - source_location: Option, + source_location: Option, } impl LogBuilder { fn new(message: &str) -> Self { LogBuilder { - severity: models::LogSeverity::Info, + severity: spec::LogSeverity::Info, message: message.to_string(), source_location: None, } } - pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { + pub fn severity(mut self, value: spec::LogSeverity) -> LogBuilder { self.severity = value; self } pub fn source(mut self, file: &str, line: i32) -> LogBuilder { - self.source_location = Some(models::SourceLocationSpec { + self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, }); @@ -68,19 +67,18 @@ mod tests { use anyhow::Result; use super::*; - use crate::output as tv; - use tv::models; + use crate::spec; #[test] fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { let log = Log::builder("test") - .severity(models::LogSeverity::Info) + .severity(spec::LogSeverity::Info) .build(); let artifact = log.to_artifact(); assert_eq!( artifact, - models::LogSpec { + spec::Log { severity: log.severity.clone(), message: log.message.clone(), source_location: log.source_location.clone(), @@ -93,13 +91,13 @@ mod tests { #[test] fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { let log = Log::builder("test") - .severity(models::LogSeverity::Info) + .severity(spec::LogSeverity::Info) .build(); let artifact = log.to_artifact(); assert_eq!( artifact, - models::LogSpec { + spec::Log { severity: log.severity.clone(), message: log.message.clone(), source_location: log.source_location.clone(), diff --git a/src/output/measurement.rs b/src/output/measurement.rs index 5badd7f..249df84 100644 --- a/src/output/measurement.rs +++ b/src/output/measurement.rs @@ -14,7 +14,8 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::{dut, emitter, models, state}; +use crate::spec; +use tv::{dut, emitter, state}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -242,17 +243,17 @@ impl MeasurementSeries { #[derive(Clone)] pub struct Validator { name: Option, - validator_type: models::ValidatorType, + validator_type: spec::ValidatorType, value: Value, metadata: Option>, } impl Validator { - pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { + pub fn builder(validator_type: spec::ValidatorType, value: Value) -> ValidatorBuilder { ValidatorBuilder::new(validator_type, value) } - pub fn to_spec(&self) -> models::ValidatorSpec { - models::ValidatorSpec { + pub fn to_spec(&self) -> spec::Validator { + spec::Validator { name: self.name.clone(), validator_type: self.validator_type.clone(), value: self.value.clone(), @@ -264,13 +265,13 @@ impl Validator { #[derive(Debug)] pub struct ValidatorBuilder { name: Option, - validator_type: models::ValidatorType, + validator_type: spec::ValidatorType, value: Value, metadata: Option>, } impl ValidatorBuilder { - fn new(validator_type: models::ValidatorType, value: Value) -> Self { + fn new(validator_type: spec::ValidatorType, value: Value) -> Self { ValidatorBuilder { validator_type, value: value.clone(), @@ -405,9 +406,9 @@ impl Measurement { /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { name: self.name.clone(), unit: self.unit.clone(), value: self.value.clone(), @@ -633,10 +634,10 @@ impl MeasurementSeriesStart { MeasurementSeriesStartBuilder::new(name, series_id) } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name: self.name.clone(), unit: self.unit.clone(), series_id: self.series_id.clone(), @@ -758,10 +759,10 @@ impl MeasurementSeriesEnd { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( + spec::MeasurementSeriesEnd { series_id: self.series_id.clone(), total_count: self.total_count, }, @@ -794,10 +795,10 @@ impl MeasurementSeriesElement { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( - models::MeasurementSeriesElementSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesElement( + spec::MeasurementSeriesElement { index: self.index, value: self.value.clone(), timestamp: self.timestamp, @@ -813,8 +814,9 @@ impl MeasurementSeriesElement { mod tests { use super::*; use crate::output as tv; + use crate::spec; + use tv::dut::*; use tv::ValidatorType; - use tv::{dut::*, models}; use anyhow::{bail, Result}; @@ -827,18 +829,16 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name: name.to_string(), - unit: None, - value, - validators: None, - hardware_info_id: None, - subcomponent: None, - metadata: None, - } - ), + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + }), }) ); @@ -850,7 +850,7 @@ mod tests { let name = "name".to_owned(); let value = Value::from(50000); let hardware_info = HardwareInfo::builder("id", "name").build(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); let meta_key = "key"; let meta_value = Value::from("value"); @@ -874,18 +874,16 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name, - unit: Some(unit.to_string()), - value, - validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(metadata), - } - ), + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { + name, + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + }), }) ); @@ -901,9 +899,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name: name.to_string(), unit: None, series_id: series_id.to_string(), @@ -923,8 +921,8 @@ mod tests { fn test_measurement_series_start_builder_to_artifact() -> Result<()> { let name = "name".to_owned(); let series_id = "series_id".to_owned(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); + let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); + let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); let hw_info = HardwareInfo::builder("id", "name").build(); let subcomponent = Subcomponent::builder("name").build(); let series = MeasurementSeriesStart::builder(&name, &series_id) @@ -940,9 +938,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name, unit: Some("unit".to_string()), series_id: series_id.to_string(), @@ -969,9 +967,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( + spec::MeasurementSeriesEnd { series_id: series_id.to_string(), total_count: 1, } diff --git a/src/output/mod.rs b/src/output/mod.rs index 6326631..17cd5f4 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -11,22 +11,21 @@ mod error; mod log; mod macros; mod measurement; -mod models; mod run; mod state; mod step; +pub use crate::spec::LogSeverity; +pub use crate::spec::TestResult; +pub use crate::spec::TestStatus; +pub use crate::spec::ValidatorType; +pub use crate::spec::SPEC_VERSION; pub use config::*; pub use dut::*; pub use emitter::*; pub use error::*; pub use log::*; pub use measurement::*; -pub use models::LogSeverity; -pub use models::TestResult; -pub use models::TestStatus; -pub use models::ValidatorType; -pub use models::SPEC_VERSION; pub use run::*; pub use step::*; diff --git a/src/output/run.rs b/src/output/run.rs index dce57f5..c3cc844 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -12,16 +12,17 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; +use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log, models, run, state}; +use tv::{config, dut, emitter, error, log, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. pub struct TestRunOutcome { /// Reports the execution status of the test - pub status: models::TestStatus, + pub status: spec::TestStatus, /// Reports the result of the test - pub result: models::TestResult, + pub result: spec::TestResult, } /// The main diag test run. @@ -303,8 +304,8 @@ impl StartedTestRun { /// ``` pub async fn end( &self, - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, ) -> Result<(), emitter::WriterError> { let end = run::TestRunEnd::builder() .status(status) @@ -341,18 +342,18 @@ impl StartedTestRun { /// ``` pub async fn log( &self, - severity: models::LogSeverity, + severity: spec::LogSeverity, msg: &str, ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -384,11 +385,11 @@ impl StartedTestRun { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -416,11 +417,11 @@ impl StartedTestRun { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -453,11 +454,11 @@ impl StartedTestRun { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -493,11 +494,11 @@ impl StartedTestRun { ) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -528,9 +529,9 @@ impl TestRunStart { TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::TestRunStart(spec::TestRunStart { name: self.name.clone(), version: self.version.clone(), command_line: self.command_line.clone(), @@ -597,8 +598,8 @@ impl TestRunStartBuilder { } pub struct TestRunEnd { - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, } impl TestRunEnd { @@ -606,9 +607,9 @@ impl TestRunEnd { TestRunEndBuilder::new() } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::TestRunEnd(spec::TestRunEnd { status: self.status.clone(), result: self.result.clone(), }), @@ -618,24 +619,24 @@ impl TestRunEnd { #[derive(Debug)] pub struct TestRunEndBuilder { - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, } #[allow(clippy::new_without_default)] impl TestRunEndBuilder { pub fn new() -> TestRunEndBuilder { TestRunEndBuilder { - status: models::TestStatus::Complete, - result: models::TestResult::Pass, + status: spec::TestStatus::Complete, + result: spec::TestResult::Pass, } } - pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { + pub fn status(mut self, value: spec::TestStatus) -> TestRunEndBuilder { self.status = value; self } - pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { + pub fn result(mut self, value: spec::TestResult) -> TestRunEndBuilder { self.result = value; self } @@ -658,13 +659,13 @@ pub struct SchemaVersion { impl SchemaVersion { pub fn new() -> SchemaVersion { SchemaVersion { - major: models::SPEC_VERSION.0, - minor: models::SPEC_VERSION.1, + major: spec::SPEC_VERSION.0, + minor: spec::SPEC_VERSION.1, } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::SchemaVersion(models::SchemaVersionSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::SchemaVersion(spec::SchemaVersion { major: self.major, minor: self.minor, }) @@ -676,14 +677,13 @@ mod tests { use anyhow::Result; use super::*; - use crate::output as tv; - use tv::models; + use crate::spec; #[test] fn test_schema_creation_from_builder() -> Result<()> { let version = SchemaVersion::new(); - assert_eq!(version.major, models::SPEC_VERSION.0); - assert_eq!(version.minor, models::SPEC_VERSION.1); + assert_eq!(version.major, spec::SPEC_VERSION.0); + assert_eq!(version.minor, spec::SPEC_VERSION.1); Ok(()) } } diff --git a/src/output/step.rs b/src/output/step.rs index de8da4d..20fa755 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -10,8 +10,9 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; +use crate::spec; use tv::measurement::MeasurementSeries; -use tv::{emitter, error, log, measurement, models, state, step}; +use tv::{emitter, error, log, measurement, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -123,7 +124,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitter::WriterError> { + pub async fn end(&self, status: spec::TestStatus) -> Result<(), emitter::WriterError> { let end = step::TestStepEnd::new(status); self.step .state @@ -178,17 +179,17 @@ impl StartedTestStep { /// ``` pub async fn log( &self, - severity: models::LogSeverity, + severity: spec::LogSeverity, msg: &str, ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -222,11 +223,11 @@ impl StartedTestStep { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -274,11 +275,11 @@ impl StartedTestStep { let error = error::Error::builder(symptom).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -331,11 +332,11 @@ impl StartedTestStep { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -373,11 +374,11 @@ impl StartedTestStep { ) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -527,29 +528,27 @@ impl TestStepStart { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepStart( - models::TestStepStartSpec { - name: self.name.clone(), - }, - ), + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::TestStepStart(spec::TestStepStart { + name: self.name.clone(), + }), }) } } pub struct TestStepEnd { - status: models::TestStatus, + status: spec::TestStatus, } impl TestStepEnd { - pub fn new(status: models::TestStatus) -> TestStepEnd { + pub fn new(status: spec::TestStatus) -> TestStepEnd { TestStepEnd { status } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::TestStepEnd(spec::TestStepEnd { status: self.status.clone(), }), }) diff --git a/src/output/models.rs b/src/spec.rs similarity index 91% rename from src/output/models.rs rename to src/spec.rs index db187d1..d60baae 100644 --- a/src/output/models.rs +++ b/src/spec.rs @@ -41,65 +41,65 @@ mod rfc3339_format { #[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestRunArtifactDescendant { #[serde(rename = "testRunStart")] - TestRunStart(TestRunStartSpec), + TestRunStart(TestRunStart), #[serde(rename = "testRunEnd")] - TestRunEnd(TestRunEndSpec), + TestRunEnd(TestRunEnd), #[serde(rename = "log")] - Log(LogSpec), + Log(Log), #[serde(rename = "error")] - Error(ErrorSpec), + Error(Error), } #[derive(Debug, Serialize, PartialEq, Clone)] -pub enum RootArtifactSpec { +pub enum RootArtifact { #[serde(rename = "schemaVersion")] - SchemaVersion(SchemaVersionSpec), + SchemaVersion(SchemaVersion), #[serde(rename = "testRunArtifact")] - TestRunArtifact(TestRunArtifactSpec), + TestRunArtifact(TestRunArtifact), #[serde(rename = "testStepArtifact")] - TestStepArtifact(TestStepArtifactSpec), + TestStepArtifact(TestStepArtifact), } #[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] - TestStepStart(TestStepStartSpec), + TestStepStart(TestStepStart), #[serde(rename = "testStepEnd")] - TestStepEnd(TestStepEndSpec), + TestStepEnd(TestStepEnd), #[serde(rename = "measurement")] - Measurement(MeasurementSpec), + Measurement(Measurement), #[serde(rename = "measurementSeriesStart")] - MeasurementSeriesStart(MeasurementSeriesStartSpec), + MeasurementSeriesStart(MeasurementSeriesStart), #[serde(rename = "measurementSeriesEnd")] - MeasurementSeriesEnd(MeasurementSeriesEndSpec), + MeasurementSeriesEnd(MeasurementSeriesEnd), #[serde(rename = "measurementSeriesElement")] - MeasurementSeriesElement(MeasurementSeriesElementSpec), + MeasurementSeriesElement(MeasurementSeriesElement), #[serde(rename = "diagnosis")] - Diagnosis(DiagnosisSpec), + Diagnosis(Diagnosis), #[serde(rename = "log")] - Log(LogSpec), + Log(Log), #[serde(rename = "error")] - Error(ErrorSpec), + Error(Error), #[serde(rename = "file")] - File(FileSpec), + File(File), #[serde(rename = "extension")] - Extension(ExtensionSpec), + Extension(Extension), } #[derive(Debug, Serialize, Clone, PartialEq)] @@ -234,9 +234,9 @@ pub enum SoftwareType { } #[derive(Debug, Serialize, Clone)] -pub struct RootSpec { +pub struct Root { #[serde(flatten)] - pub artifact: RootArtifactSpec, + pub artifact: RootArtifact, // TODO : manage different timezones #[serde(rename = "timestamp")] @@ -254,7 +254,7 @@ pub struct RootSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "schemaVersion")] -pub struct SchemaVersionSpec { +pub struct SchemaVersion { #[serde(rename = "major")] pub major: i8, @@ -268,7 +268,7 @@ pub struct SchemaVersionSpec { /// 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 #[derive(Debug, Serialize, PartialEq, Clone)] -pub struct TestRunArtifactSpec { +pub struct TestRunArtifact { #[serde(flatten)] pub artifact: TestRunArtifactDescendant, } @@ -280,7 +280,7 @@ pub struct TestRunArtifactSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunStart")] -pub struct TestRunStartSpec { +pub struct TestRunStart { #[serde(rename = "name")] pub name: String, @@ -294,7 +294,7 @@ pub struct TestRunStartSpec { pub parameters: Map, #[serde(rename = "dutInfo")] - pub dut_info: DutInfoSpec, + pub dut_info: DutInfo, #[serde(rename = "metadata")] pub metadata: Option>, @@ -307,7 +307,7 @@ pub struct TestRunStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "dutInfo")] -pub struct DutInfoSpec { +pub struct DutInfo { #[serde(rename = "dutInfoId")] pub id: String, @@ -315,13 +315,13 @@ pub struct DutInfoSpec { pub name: Option, #[serde(rename = "platformInfos")] - pub platform_infos: Option>, + pub platform_infos: Option>, #[serde(rename = "softwareInfos")] - pub software_infos: Option>, + pub software_infos: Option>, #[serde(rename = "hardwareInfos")] - pub hardware_infos: Option>, + pub hardware_infos: Option>, #[serde(rename = "metadata")] pub metadata: Option>, @@ -334,7 +334,7 @@ pub struct DutInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "platformInfo")] -pub struct PlatformInfoSpec { +pub struct PlatformInfo { #[serde(rename = "info")] pub info: String, } @@ -346,7 +346,7 @@ pub struct PlatformInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareInfo")] -pub struct SoftwareInfoSpec { +pub struct SoftwareInfo { #[serde(rename = "softwareInfoId")] pub id: String, @@ -373,7 +373,7 @@ pub struct SoftwareInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "hardwareInfo")] -pub struct HardwareInfoSpec { +pub struct HardwareInfo { #[serde(rename = "hardwareInfoId")] pub id: String, @@ -418,7 +418,7 @@ pub struct HardwareInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunEnd")] -pub struct TestRunEndSpec { +pub struct TestRunEnd { #[serde(rename = "status")] pub status: TestStatus, @@ -434,7 +434,7 @@ pub struct TestRunEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] -pub struct ErrorSpec { +pub struct Error { #[serde(rename = "symptom")] pub symptom: String, @@ -443,10 +443,10 @@ pub struct ErrorSpec { // TODO: support this field during serialization to print only the id of SoftwareInfo struct #[serde(rename = "softwareInfoIds")] - pub software_infos: Option>, + pub software_infos: Option>, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Low-level model for `log` spec object. @@ -456,7 +456,7 @@ pub struct ErrorSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "log")] -pub struct LogSpec { +pub struct Log { #[serde(rename = "severity")] pub severity: LogSeverity, @@ -464,7 +464,7 @@ pub struct LogSpec { pub message: String, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Provides information about which file/line of the source code in @@ -474,7 +474,7 @@ pub struct LogSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation #[derive(Debug, Serialize, Clone, Default, PartialEq)] #[serde(rename = "sourceLocation")] -pub struct SourceLocationSpec { +pub struct SourceLocation { #[serde(rename = "file")] pub file: String, @@ -488,7 +488,7 @@ pub struct SourceLocationSpec { /// 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 #[derive(Debug, Serialize, PartialEq, Clone)] -pub struct TestStepArtifactSpec { +pub struct TestStepArtifact { #[serde(flatten)] pub descendant: TestStepArtifactDescendant, } @@ -500,7 +500,7 @@ pub struct TestStepArtifactSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepStart")] -pub struct TestStepStartSpec { +pub struct TestStepStart { #[serde(rename = "name")] pub name: String, } @@ -512,7 +512,7 @@ pub struct TestStepStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepEnd")] -pub struct TestStepEndSpec { +pub struct TestStepEnd { #[serde(rename = "status")] pub status: TestStatus, } @@ -524,7 +524,7 @@ pub struct TestStepEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] -pub struct MeasurementSpec { +pub struct Measurement { #[serde(rename = "name")] pub name: String, @@ -535,13 +535,13 @@ pub struct MeasurementSpec { pub unit: Option, #[serde(rename = "validators")] - pub validators: Option>, + pub validators: Option>, #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, #[serde(rename = "subcomponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "metadata")] pub metadata: Option>, @@ -554,7 +554,7 @@ pub struct MeasurementSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] -pub struct ValidatorSpec { +pub struct Validator { #[serde(rename = "name")] pub name: Option, @@ -575,7 +575,7 @@ pub struct ValidatorSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] -pub struct SubcomponentSpec { +pub struct Subcomponent { #[serde(rename = "type")] pub subcomponent_type: Option, @@ -599,7 +599,7 @@ pub struct SubcomponentSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] -pub struct MeasurementSeriesStartSpec { +pub struct MeasurementSeriesStart { #[serde(rename = "name")] pub name: String, @@ -610,13 +610,13 @@ pub struct MeasurementSeriesStartSpec { pub series_id: String, #[serde(rename = "validators")] - pub validators: Option>, + pub validators: Option>, #[serde(rename = "hardwareInfoId")] - pub hardware_info: Option, + pub hardware_info: Option, #[serde(rename = "subComponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "metadata")] pub metadata: Option>, @@ -629,7 +629,7 @@ pub struct MeasurementSeriesStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesEnd")] -pub struct MeasurementSeriesEndSpec { +pub struct MeasurementSeriesEnd { #[serde(rename = "measurementSeriesId")] pub series_id: String, @@ -644,7 +644,7 @@ pub struct MeasurementSeriesEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesElement #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesElement")] -pub struct MeasurementSeriesElementSpec { +pub struct MeasurementSeriesElement { #[serde(rename = "index")] pub index: u64, @@ -668,7 +668,7 @@ pub struct MeasurementSeriesElementSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "diagnosis")] -pub struct DiagnosisSpec { +pub struct Diagnosis { #[serde(rename = "verdict")] pub verdict: String, @@ -679,13 +679,13 @@ pub struct DiagnosisSpec { pub message: Option, #[serde(rename = "validators")] - pub hardware_info: Option, + pub hardware_info: Option, #[serde(rename = "subComponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Low-level model for the `file` spec object. @@ -695,7 +695,7 @@ pub struct DiagnosisSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] -pub struct FileSpec { +pub struct File { #[serde(rename = "name")] pub name: String, @@ -722,7 +722,7 @@ pub struct FileSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "extension")] -pub struct ExtensionSpec { +pub struct Extension { #[serde(rename = "name")] pub name: String, @@ -742,7 +742,7 @@ mod tests { #[test] fn test_rfc3339_format_serialize() -> Result<()> { let test_date = "2022-01-01T00:00:00.000Z"; - let msr = MeasurementSeriesElementSpec { + let msr = MeasurementSeriesElement { index: 0, value: 1.0.into(), timestamp: DateTime::parse_from_rfc3339(test_date)?.with_timezone(&chrono_tz::UTC), @@ -762,7 +762,7 @@ mod tests { let test_date = "2022-01-01T00:00:00.000Z"; let json = json!({"index":0,"measurementSeriesId":"test","metadata":null,"timestamp":"2022-01-01T00:00:00.000Z","value":1.0}); - let msr = serde_json::from_value::(json)?; + let msr = serde_json::from_value::(json)?; assert_eq!( msr.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), test_date