From d3f4bb4fc5c7f39deb46326ec7a3260b90bfa63d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 12:32:27 +0100 Subject: [PATCH] add StartedTestRun type state - this new type disallows users to misuse the run (eg. by emitting run artifacts when the run hasn't been started yet) - this also shows that there is little value in having a TestRun object around, while not having started, due to the only action possible on it is to start it. It may be beneficial in the future to only allow started objects to exist, instead of having inert objects passed around. This is a slight change in usage, but not very consequential. - also fix macros tests since this new StartedTestRun pattern highlighted that they were emitting invalid messages Signed-off-by: mimir-d --- src/output/macros.rs | 236 ++++++++++--------- src/output/runner.rs | 523 ++++++++++++++++++++--------------------- tests/output/runner.rs | 111 ++++----- 3 files changed, 444 insertions(+), 426 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index 39ab47a..9f49d03 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -27,8 +27,7 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_error!(test_run, "symptom"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -44,8 +43,7 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_error!(test_run, "symptom", "Error message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -54,7 +52,7 @@ /// ``` #[macro_export] macro_rules! ocptv_error { - ($runner:expr , $symptom:expr, $msg:expr) => { + ($runner:expr, $symptom:expr, $msg:expr) => { async { $runner .error_with_details( @@ -99,8 +97,7 @@ macro_rules! ocptv_error { /// /// use ocptv::ocptv_log_debug; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_log_debug!(test_run, "Log message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -210,23 +207,25 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_error!(test_run, "symptom", "Error message").await?; + ocptv_error!(run, "symptom", "Error message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing error log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -252,23 +251,25 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_error!(test_run, "symptom").await?; + ocptv_error!(run, "symptom").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing error log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -295,23 +296,25 @@ mod tests { "severity": "DEBUG" } }, - "sequenceNumber":1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_debug!(test_run, "log message").await?; + ocptv_log_debug!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -338,23 +341,25 @@ mod tests { "severity": "INFO" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_info!(test_run, "log message").await?; + ocptv_log_info!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -382,23 +387,25 @@ mod tests { "severity": "WARNING" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_warning!(test_run, "log message").await?; + ocptv_log_warning!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -425,23 +432,25 @@ mod tests { "severity": "ERROR" } }, - "sequenceNumber":1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_error!(test_run, "log message").await?; + ocptv_log_error!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -468,23 +477,25 @@ mod tests { "severity": "FATAL" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_fatal!(test_run, "log message").await?; + ocptv_log_fatal!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -511,25 +522,28 @@ mod tests { "symptom":"symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_error!(step, "symptom", "Error message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -555,25 +569,27 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_error!(step, "symptom").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -600,24 +616,26 @@ mod tests { "severity": "DEBUG" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_debug!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -644,24 +662,26 @@ mod tests { "severity": "INFO" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_info!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -688,24 +708,26 @@ mod tests { "severity":"WARNING" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_warning!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -732,24 +754,26 @@ mod tests { "severity": "ERROR" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_error!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -776,24 +800,26 @@ mod tests { "severity": "FATAL" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_fatal!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); diff --git a/src/output/runner.rs b/src/output/runner.rs index 976b1f4..547d644 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -109,10 +109,8 @@ impl TestState { } /// The main diag test run. -/// This object describes a single run instance of the diag, and therefore drives the test session. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart - +/// This object describes a single run instance of the diag, and therefore drives the test session. pub struct TestRun { name: String, version: String, @@ -145,7 +143,7 @@ impl TestRun { /// ```rust /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// 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); @@ -163,13 +161,13 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// run.start().await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { + pub async fn start(self) -> Result { let version = objects::SchemaVersion::new(); self.state .lock() @@ -199,9 +197,178 @@ impl TestRun { .emitter .emit(&start.to_artifact()) .await?; - Ok(()) + + 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 @@ -212,9 +379,8 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -228,48 +394,10 @@ impl TestRun { .status(status) .result(result) .build(); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - /// 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 test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_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<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a TestRun) -> R, - { - self.start().await?; - let outcome = func(self).await?; - self.end(outcome.status, outcome.result).await?; + let emitter = &self.run.state.lock().await.emitter; + + emitter.emit(&end.to_artifact()).await?; Ok(()) } @@ -285,13 +413,12 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.log( + /// 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?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -302,10 +429,10 @@ impl TestRun { msg: &str, ) -> Result<(), emitters::WriterError> { let log = objects::Log::builder(msg).severity(severity).build(); - self.state - .lock() - .await - .emitter + + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -322,24 +449,22 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.log_with_details( + /// 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?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -356,20 +481,18 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error("symptom").await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// 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(); - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -387,10 +510,9 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error_with_msg("symptom", "error messasge").await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// 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>(()) /// # }); @@ -401,10 +523,9 @@ impl TestRun { msg: &str, ) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).message(msg).build(); - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -421,16 +542,15 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error_with_details( + /// 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?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -439,136 +559,16 @@ impl TestRun { &self, error: &objects::Error, ) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) } pub fn step(&self, name: &str) -> Result { - Ok(TestStep::new(name, self.state.clone())) - } -} - -/// 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 test_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 test_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 test_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 test_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)), - } + Ok(TestStep::new(name, self.run.state.clone())) } } @@ -600,10 +600,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// # Ok::<(), WriterError>(()) @@ -630,10 +629,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.end(TestStatus::Complete).await?; /// @@ -664,10 +662,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("first step")?; + /// let step = run.step("first step")?; /// step.scope(|s| async { /// s.log( /// LogSeverity::Info, @@ -702,10 +699,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.log( /// LogSeverity::Info, @@ -724,10 +720,9 @@ impl TestStep { /// /// use ocptv::ocptv_log_info; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; /// step.end(TestStatus::Complete).await?; @@ -761,10 +756,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.log_with_details( /// &Log::builder("This is a log message with INFO severity") @@ -798,8 +792,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error("symptom").await?; /// step.end(TestStatus::Complete).await?; @@ -816,10 +811,9 @@ impl TestStep { /// /// use ocptv::ocptv_error; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_error!(step, "symptom").await?; /// step.end(TestStatus::Complete).await?; @@ -850,8 +844,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error_with_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; @@ -868,9 +863,9 @@ impl TestStep { /// /// use ocptv::ocptv_error; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_error!(step, "symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; @@ -904,8 +899,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error_with_details( /// &Error::builder("symptom") @@ -942,8 +938,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.add_measurement("name", 50.into()).await?; /// step.end(TestStatus::Complete).await?; @@ -978,9 +975,8 @@ impl TestStep { /// # use ocptv::output::*; /// /// let hwinfo = HardwareInfo::builder("id", "fan").build(); - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; - /// step.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// /// let measurement = Measurement::builder("name", 5000.into()) /// .hardware_info(&hwinfo) @@ -1019,8 +1015,8 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// step.start().await?; /// let series = step.measurement_series("name"); /// @@ -1049,8 +1045,8 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// step.start().await?; /// let series = /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); @@ -1117,10 +1113,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1149,10 +1144,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1186,10 +1180,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1227,10 +1220,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1276,10 +1268,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 55307e1..242708a 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(unused_imports)] use std::fs; use std::sync::Arc; @@ -20,8 +21,8 @@ use tokio::sync::Mutex; use ocptv::output as tv; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, - SoftwareInfo, Subcomponent, TestResult, TestRun, TestRunBuilder, TestRunOutcome, TestStatus, - TestStep, Validator, ValidatorType, + SoftwareInfo, StartedTestRun, Subcomponent, TestResult, TestRun, TestRunBuilder, + TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, }; fn json_schema_version() -> serde_json::Value { @@ -113,12 +114,12 @@ where async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a TestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, + F: for<'a> FnOnce(&'a StartedTestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, { check_output(expected, |run_builder| async { let run = run_builder.build(); - run.start().await?; + let run = run.start().await?; test_fn(&run).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -132,8 +133,7 @@ where F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, { check_output(expected, |run_builder| async { - let run = run_builder.build(); - run.start().await?; + let run = run_builder.build().start().await?; let step = run.step("first step")?; step.start().await?; @@ -310,39 +310,39 @@ async fn test_testrun_with_error_with_details() -> Result<()> { .await } -#[tokio::test] -async fn test_testrun_with_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "First message", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output(&expected, |run_builder| async { - let run = run_builder.build(); - - run.scope(|r| async { - r.log(LogSeverity::Info, "First message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - }) - .await?; - - Ok(()) - }) - .await -} +// #[tokio::test] +// async fn test_testrun_with_scope() -> Result<()> { +// let expected = [ +// json_schema_version(), +// json_run_default_start(), +// json!({ +// "testRunArtifact": { +// "log": { +// "message": "First message", +// "severity": "INFO" +// } +// }, +// "sequenceNumber": 3 +// }), +// json_run_pass(4), +// ]; + +// check_output(&expected, |run_builder| async { +// let run = run_builder.build(); + +// run.scope(|r| async { +// r.log(LogSeverity::Info, "First message").await?; +// Ok(TestRunOutcome { +// status: TestStatus::Complete, +// result: TestResult::Pass, +// }) +// }) +// .await?; + +// Ok(()) +// }) +// .await +// } #[tokio::test] async fn test_testrun_with_step() -> Result<()> { @@ -1235,17 +1235,13 @@ async fn test_config_builder_with_file() -> Result<()> { .await? .build(), ) - .build(); + .build() + .start() + .await?; - run.scope(|r| async { - r.error_with_msg("symptom", "Error message").await?; + run.error_with_msg("symptom", "Error message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - }) - .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; output_file.assert(predicate::path::exists()); let content = fs::read_to_string(output_file.path())?; @@ -1267,9 +1263,8 @@ async fn test_testrun_instantiation_with_new() -> Result<()> { ]; let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::new("run_name", "dut_id", "1.0"); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + let run = TestRun::new("run_name", "dut_id", "1.0").start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; @@ -1301,8 +1296,12 @@ async fn test_testrun_metadata() -> Result<()> { ]; check_output(&expected, |run_builder| async { - let run = run_builder.add_metadata("key", "value".into()).build(); - run.start().await?; + let run = run_builder + .add_metadata("key", "value".into()) + .build() + .start() + .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) }) @@ -1342,8 +1341,10 @@ async fn test_testrun_builder() -> Result<()> { .add_metadata("key2", "value2".into()) .add_parameter("key", "value".into()) .command_line("cmd_line") - .build(); - run.start().await?; + .build() + .start() + .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) })