diff --git a/src/output/config.rs b/src/output/config.rs index d5c27b8..0c98dfc 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -12,7 +12,7 @@ use crate::output::emitter; /// The configuration repository for the TestRun. pub struct Config { - pub(crate) timezone: chrono_tz::Tz, + pub(crate) timestamp_provider: Box, pub(crate) writer: emitter::WriterType, } @@ -32,20 +32,28 @@ impl Config { /// The builder for the [`Config`] object. pub struct ConfigBuilder { - timezone: Option, + timestamp_provider: Box, writer: Option, } impl ConfigBuilder { fn new() -> Self { Self { - timezone: None, + timestamp_provider: Box::new(ConfiguredTzProvider { tz: chrono_tz::UTC }), writer: Some(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { - self.timezone = Some(timezone); + self.timestamp_provider = Box::new(ConfiguredTzProvider { tz: timezone }); + self + } + + pub fn with_timestamp_provider( + mut self, + timestamp_provider: Box, + ) -> Self { + self.timestamp_provider = timestamp_provider; self } @@ -68,10 +76,32 @@ impl ConfigBuilder { pub fn build(self) -> Config { Config { - timezone: self.timezone.unwrap_or(chrono_tz::UTC), + timestamp_provider: self.timestamp_provider, writer: self .writer .unwrap_or(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } } + +pub trait TimestampProvider { + fn now(&self) -> chrono::DateTime; +} + +struct ConfiguredTzProvider { + tz: chrono_tz::Tz, +} + +impl TimestampProvider for ConfiguredTzProvider { + fn now(&self) -> chrono::DateTime { + chrono::Local::now().with_timezone(&self.tz) + } +} + +pub struct NullTimestampProvider {} + +impl TimestampProvider for NullTimestampProvider { + fn now(&self) -> chrono::DateTime { + chrono::DateTime::from_timestamp_nanos(0).with_timezone(&chrono_tz::UTC) + } +} diff --git a/src/output/emitter.rs b/src/output/emitter.rs index ab738c8..4e899c6 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -16,6 +16,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; +use crate::output::config; use crate::spec; #[derive(Debug, thiserror::Error, derive_more::Display)] @@ -84,26 +85,28 @@ impl StdoutWriter { } pub struct JsonEmitter { - timezone: chrono_tz::Tz, + // HACK: public for tests, but this should come from config directly to where needed + pub(crate) timestamp_provider: Box, writer: WriterType, seqno: Arc, } impl JsonEmitter { - pub(crate) fn new(timezone: chrono_tz::Tz, writer: WriterType) -> Self { + pub(crate) fn new( + timestamp_provider: Box, + writer: WriterType, + ) -> Self { JsonEmitter { - timezone, + timestamp_provider, writer, seqno: Arc::new(atomic::AtomicU64::new(0)), } } fn serialize_artifact(&self, object: &spec::RootImpl) -> serde_json::Value { - let now = chrono::Local::now(); - let now_tz = now.with_timezone(&self.timezone); let root = spec::Root { artifact: object.clone(), - timestamp: now_tz, + timestamp: self.timestamp_provider.now(), seqno: self.incr_seqno(), }; serde_json::json!(root) @@ -144,7 +147,10 @@ mod tests { let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); - let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + let emitter = JsonEmitter::new( + Box::new(config::NullTimestampProvider {}), + WriterType::Buffer(writer), + ); emitter .emit(&spec::RootImpl::SchemaVersion( @@ -179,7 +185,10 @@ mod tests { let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); - let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + let emitter = JsonEmitter::new( + Box::new(config::NullTimestampProvider {}), + WriterType::Buffer(writer), + ); let version = spec::RootImpl::SchemaVersion(spec::SchemaVersion::default()); emitter.emit(&version).await?; diff --git a/src/output/error.rs b/src/output/error.rs index 22b1d17..1fbe845 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -82,8 +82,7 @@ impl ErrorBuilder { #[cfg(test)] mod tests { use anyhow::Result; - - use assert_json_diff::assert_json_include; + use assert_json_diff::assert_json_eq; use super::*; use crate::output as tv; @@ -140,45 +139,30 @@ mod tests { "message": "message", "softwareInfoIds": [ { - "computerSystem": null, "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null + "softwareInfoId": "software_id", }, { - "computerSystem": null, "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null + "softwareInfoId": "software_id", } ], - "sourceLocation": {"file": "file.rs", "line": 1}, + "sourceLocation": { + "file": "file.rs", + "line": 1 + }, "symptom": "symptom" }); let expected_step = serde_json::json!({ "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}, @@ -195,11 +179,11 @@ mod tests { let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_run); + assert_json_eq!(actual, expected_run); let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_step); + assert_json_eq!(actual, expected_step); Ok(()) } diff --git a/src/output/measure.rs b/src/output/measure.rs index bed5a94..fe8acb5 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -180,7 +180,7 @@ impl StartedMeasurementSeries { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + timestamp: self.parent.emitter.timestamp_provider().now(), series_id: self.parent.start.series_id.clone(), metadata: None, }; @@ -223,7 +223,7 @@ impl StartedMeasurementSeries { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + timestamp: self.parent.emitter.timestamp_provider().now(), series_id: self.parent.start.series_id.clone(), metadata: Some(Map::from_iter( metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), @@ -737,24 +737,6 @@ impl MeasurementSeriesStartBuilder { } } -// pub struct MeasurementSeriesEmitter { -// series_id: String, -// step_emitter: Arc, -// } - -// impl StepEmitter { -// pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), WriterError> { -// let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { -// id: self.step_id.clone(), -// // TODO: can these copies be avoided? -// artifact: object.clone(), -// }); -// self.run_emitter.emit(&root).await?; - -// Ok(()) -// } -// } - #[cfg(test)] mod tests { use super::*; diff --git a/src/output/run.rs b/src/output/run.rs index f72bb22..1e9192e 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -258,7 +258,7 @@ impl TestRunBuilder { pub fn build(self) -> TestRun { let config = self.config.unwrap_or(config::Config::builder().build()); - let emitter = emitter::JsonEmitter::new(config.timezone, config.writer); + let emitter = emitter::JsonEmitter::new(config.timestamp_provider, config.writer); TestRun { name: self.name, diff --git a/src/output/step.rs b/src/output/step.rs index bf04dc6..c048d54 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -14,8 +14,8 @@ use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; use tv::{emitter, error, log, measure}; -use super::JsonEmitter; use super::WriterError; +use super::{JsonEmitter, TimestampProvider}; /// A single test step in the scope of a [`TestRun`]. /// @@ -515,4 +515,8 @@ impl StepEmitter { Ok(()) } + + pub fn timestamp_provider(&self) -> &dyn TimestampProvider { + &*self.run_emitter.timestamp_provider + } } diff --git a/src/spec.rs b/src/spec.rs index 545d306..b6ce19f 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -273,6 +273,7 @@ pub struct TestRunStart { #[serde(rename = "dutInfo")] pub dut_info: DutInfo, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -288,18 +289,23 @@ pub struct DutInfo { #[serde(rename = "dutInfoId")] pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "name")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "platformInfos")] pub platform_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareInfos")] pub software_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfos")] pub hardware_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -330,15 +336,19 @@ pub struct SoftwareInfo { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareType")] pub software_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "computerSystem")] pub computer_system: Option, } @@ -357,33 +367,43 @@ pub struct HardwareInfo { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "location")] pub location: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "serialNumber")] pub serial_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "partNumber")] pub part_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manufacturer")] pub manufacturer: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manufacturerPartNumber")] pub manufacturer_part_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "odataId")] pub odata_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "computerSystem")] pub computer_system: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manager")] pub manager: Option, } @@ -415,13 +435,16 @@ pub struct Error { #[serde(rename = "symptom")] pub symptom: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "message")] pub message: Option, // TODO: support this field during serialization to print only the id of SoftwareInfo struct + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareInfoIds")] pub software_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -440,6 +463,7 @@ pub struct Log { #[serde(rename = "message")] pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -548,18 +572,23 @@ pub struct Measurement { #[serde(rename = "value")] pub value: Value, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "unit")] pub unit: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub validators: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -572,6 +601,7 @@ pub struct Measurement { #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] pub struct Validator { + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "name")] pub name: Option, @@ -581,6 +611,7 @@ pub struct Validator { #[serde(rename = "value")] pub value: Value, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -593,18 +624,22 @@ pub struct Validator { #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] pub struct Subcomponent { + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "type")] pub subcomponent_type: Option, #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "location")] pub location: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, } @@ -620,21 +655,26 @@ pub struct MeasurementSeriesStart { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "unit")] pub unit: Option, #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub validators: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] pub hardware_info: Option, - #[serde(rename = "subComponent")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -674,6 +714,7 @@ pub struct MeasurementSeriesElement { #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -692,15 +733,19 @@ pub struct Diagnosis { #[serde(rename = "type")] pub diagnosis_type: DiagnosisType, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "message")] pub message: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub hardware_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -722,12 +767,15 @@ pub struct File { #[serde(rename = "isSnapshot")] pub is_snapshot: bool, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "description")] pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "contentType")] pub content_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 0c58e6f..82c7b85 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use anyhow::Result; use assert_fs::prelude::*; -use assert_json_diff::assert_json_include; +use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; @@ -22,9 +22,21 @@ use ocptv::output as tv; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, SoftwareInfo, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, - TestRunBuilder, TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, + TestRunBuilder, TestRunOutcome, TestStatus, TestStep, TimestampProvider, Validator, + ValidatorType, }; +const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); +const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; +struct FixedTsProvider {} + +impl TimestampProvider for FixedTsProvider { + fn now(&self) -> chrono::DateTime { + // all cases will use time 0 but this is configurable + DATETIME.with_timezone(&chrono_tz::UTC) + } +} + fn json_schema_version() -> serde_json::Value { // seqno for schemaVersion is always 0 json!({ @@ -32,7 +44,8 @@ fn json_schema_version() -> serde_json::Value { "major": tv::SPEC_VERSION.0, "minor": tv::SPEC_VERSION.1 }, - "sequenceNumber": 0 + "sequenceNumber": 0, + "timestamp": DATETIME_FORMATTED }) } @@ -46,10 +59,12 @@ fn json_run_default_start() -> serde_json::Value { }, "name": "run_name", "parameters": {}, - "version": "1.0" + "version": "1.0", + "commandLine": "" } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }) } @@ -61,7 +76,8 @@ fn json_run_pass(seqno: i32) -> serde_json::Value { "status": "COMPLETE" } }, - "sequenceNumber": seqno + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED }) } @@ -69,22 +85,26 @@ fn json_step_default_start() -> serde_json::Value { // seqno for the default test run start is always 2 json!({ "testStepArtifact": { + "testStepId": "step_0", "testStepStart": { "name": "first step" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }) } fn json_step_complete(seqno: i32) -> serde_json::Value { json!({ "testStepArtifact": { + "testStepId": "step_0", "testStepEnd": { "status": "COMPLETE" } }, - "sequenceNumber": seqno + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED }) } @@ -98,15 +118,16 @@ where let run_builder = TestRun::builder("run_name", &dut, "1.0").config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) .build(), ); // run the main test closure test_fn(run_builder).await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { + for (i, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); + assert_json_eq!(value, expected[i]); } Ok(()) @@ -169,7 +190,8 @@ async fn test_testrun_with_log() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -203,7 +225,8 @@ async fn test_testrun_with_log_with_details() -> Result<()> { } } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -234,7 +257,8 @@ async fn test_testrun_with_error() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -257,7 +281,8 @@ async fn test_testrun_with_error_with_message() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -277,9 +302,9 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "testRunArtifact": { "error": { "message": "Error message", - "softwareInfoIds":[{ + "softwareInfoIds": [{ "name": "name", - "softwareInfoId": "id", + "softwareInfoId": "id" }], "sourceLocation": { "file": "file", @@ -288,7 +313,8 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -364,12 +390,14 @@ async fn test_testrun_step_log() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -398,6 +426,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO", @@ -407,7 +436,8 @@ async fn test_testrun_step_log_with_details() -> Result<()> { } } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -438,11 +468,13 @@ async fn test_testrun_step_error() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "error": { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -467,12 +499,14 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "error": { "message": "Error message", "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -500,7 +534,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "testStepId": "step_0", "error": { "message": "Error message", - "softwareInfoIds":[{ + "softwareInfoIds": [{ "name": "name", "softwareInfoId": "id" }], @@ -511,7 +545,8 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -588,6 +623,7 @@ async fn test_step_with_measurement() -> Result<()> { } }, "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -604,8 +640,6 @@ async fn test_step_with_measurement() -> Result<()> { .await } -// TODO: intentionally leaving these tests broken so that it's obvious later that the -// assert_json_includes was not sufficient; this case is missing `testStepId` field #[tokio::test] async fn test_step_with_measurement_builder() -> Result<()> { let expected = [ @@ -614,6 +648,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurement": { "hardwareInfoId": "id", "metadata": { @@ -623,14 +658,15 @@ async fn test_step_with_measurement_builder() -> Result<()> { "subcomponent": { "name": "name" }, - "validators":[{ + "validators": [{ "type": "EQUAL", "value": 30 }], "value": 50 } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -661,21 +697,25 @@ async fn test_step_with_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 0 } }, "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -701,39 +741,47 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_1", "name": "name" } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_1", "totalCount": 0 } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json_step_complete(7), json_run_pass(8), @@ -762,19 +810,24 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { - "measurementSeriesId": "series_id", "name": "name" + "measurementSeriesId": "series_id", + "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -803,6 +856,7 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "hardwareInfoId": { "hardwareInfoId": "id", @@ -813,25 +867,28 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "key": "value" }, "name": "name", - "subComponent": { + "subcomponent": { "name": "name" }, - "validators":[{ + "validators": [{ "type": "EQUAL", "value": 30 }] } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -867,31 +924,38 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 1 } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json_step_complete(6), json_run_pass(7), @@ -918,51 +982,64 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 1, "measurementSeriesId": "series_0", - "value": 70 + "value": 70, + "timestamp": DATETIME_FORMATTED } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 2, "measurementSeriesId": "series_0", - "value": 80 + "value": 80, + "timestamp": DATETIME_FORMATTED } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 3 } }, - "sequenceNumber": 7 + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED }), json_step_complete(8), json_run_pass(9), @@ -992,34 +1069,41 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", "metadata": { "key": "value" }, - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 1 } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json_step_complete(6), json_run_pass(7), @@ -1048,54 +1132,67 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", "name": "name" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", "metadata": {"key": "value"}, - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 1, "measurementSeriesId": "series_0", "metadata": {"key2": "value2"}, - "value": 70 + "value": 70, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 2, "measurementSeriesId": "series_0", "metadata": {"key3": "value3"}, - "value": 80 + "value": 80, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 3 } }, - "sequenceNumber": 7 + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED }), json_step_complete(8), json_run_pass(9), @@ -1217,7 +1314,8 @@ async fn test_config_builder_with_file() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -1231,6 +1329,7 @@ async fn test_config_builder_with_file() -> Result<()> { .config( Config::builder() .timezone(chrono_tz::Europe::Rome) + .with_timestamp_provider(Box::new(FixedTsProvider {})) .with_file_output(output_file.path()) .await? .build(), @@ -1287,10 +1386,13 @@ async fn test_testrun_metadata() -> Result<()> { "metadata": {"key": "value"}, "name": "run_name", "parameters": {}, - "version": "1.0" + "version": "1.0", + + "commandLine": "", } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }), json_run_pass(2), ]; @@ -1330,7 +1432,8 @@ async fn test_testrun_builder() -> Result<()> { "version": "1.0" } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }), json_run_pass(2), ];