ocptv/output/
measure.rs

1// (c) Meta Platforms, Inc. and affiliates.
2//
3// Use of this source code is governed by an MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT.
6
7use std::collections::BTreeMap;
8use std::future::Future;
9use std::sync::atomic::{self, Ordering};
10use std::sync::Arc;
11
12use delegate::delegate;
13
14use crate::output as tv;
15use crate::output::trait_ext::{MapExt, VecExt};
16use crate::spec;
17use tv::{dut, step, Ident};
18
19/// The measurement series.
20/// A Measurement Series is a time-series list of measurements.
21///
22/// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart>
23pub struct MeasurementSeries {
24    id: String,
25    detail: MeasurementSeriesDetail,
26
27    emitter: Arc<step::StepEmitter>,
28}
29
30impl MeasurementSeries {
31    // note: this object is crate public but users should only construct
32    // instances through the `StartedTestStep.add_measurement_series_*` apis
33    pub(crate) fn new(
34        series_id: &str,
35        info: MeasurementSeriesDetail,
36        emitter: Arc<step::StepEmitter>,
37    ) -> Self {
38        Self {
39            id: series_id.to_owned(),
40            detail: info,
41            emitter,
42        }
43    }
44
45    /// Starts the measurement series.
46    ///
47    /// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart>
48    ///
49    /// # Examples
50    ///
51    /// ```rust
52    /// # tokio_test::block_on(async {
53    /// # use ocptv::output::*;
54    /// let dut = DutInfo::new("my_dut");
55    /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?;
56    /// let step = run.add_step("step_name").start().await?;
57    ///
58    /// let series = step.add_measurement_series("name");
59    /// series.start().await?;
60    ///
61    /// # Ok::<(), OcptvError>(())
62    /// # });
63    /// ```
64    pub async fn start(self) -> Result<StartedMeasurementSeries, tv::OcptvError> {
65        let info = &self.detail;
66
67        let start = spec::MeasurementSeriesStart {
68            name: info.name.clone(),
69            unit: info.unit.clone(),
70            series_id: self.id.clone(),
71            validators: info.validators.map_option(Validator::to_spec),
72            hardware_info: info
73                .hardware_info
74                .as_ref()
75                .map(dut::DutHardwareInfo::to_spec),
76            subcomponent: info.subcomponent.as_ref().map(dut::Subcomponent::to_spec),
77            metadata: info.metadata.option(),
78        };
79
80        self.emitter
81            .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart(start))
82            .await?;
83
84        Ok(StartedMeasurementSeries {
85            parent: self,
86            seqno: Arc::new(atomic::AtomicU64::new(0)),
87        })
88    }
89
90    /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and
91    /// ending it. View [`MeasurementSeries::start`] and [`StartedMeasurementSeries::end`] methods.
92    /// After the scope is constructed, additional objects may be added to it.
93    /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees
94    /// all the messages are emitted between the start and end messages, the order
95    /// is respected and no messages is lost.
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// # tokio_test::block_on(async {
101    /// # use futures::FutureExt;
102    /// # use ocptv::output::*;
103    /// let dut = DutInfo::new("my_dut");
104    /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?;
105    /// let step = run.add_step("step_name").start().await?;
106    ///
107    /// let series = step.add_measurement_series("name");
108    /// series.scope(|s| {
109    ///     async move {
110    ///         s.add_measurement(60).await?;
111    ///         s.add_measurement(70).await?;
112    ///         s.add_measurement(80).await?;
113    ///         Ok(())
114    ///     }.boxed()
115    /// }).await?;
116    ///
117    /// # Ok::<(), OcptvError>(())
118    /// # });
119    /// ```
120    pub async fn scope<F, R>(self, func: F) -> Result<(), tv::OcptvError>
121    where
122        R: Future<Output = Result<(), tv::OcptvError>> + Send + 'static,
123        F: FnOnce(ScopedMeasurementSeries) -> R + Send + 'static,
124    {
125        let series = Arc::new(self.start().await?);
126        func(ScopedMeasurementSeries {
127            series: Arc::clone(&series),
128        })
129        .await?;
130        series.end_impl().await?;
131
132        Ok(())
133    }
134}
135
136/// TODO: docs
137pub struct StartedMeasurementSeries {
138    parent: MeasurementSeries,
139
140    seqno: Arc<atomic::AtomicU64>,
141}
142
143impl StartedMeasurementSeries {
144    fn incr_seqno(&self) -> u64 {
145        self.seqno.fetch_add(1, Ordering::AcqRel)
146    }
147
148    // note: keep the self-consuming method for crate api, but use this one internally,
149    // since `StartedMeasurementSeries::end` only needs to take ownership for syntactic reasons
150    async fn end_impl(&self) -> Result<(), tv::OcptvError> {
151        let end = spec::MeasurementSeriesEnd {
152            series_id: self.parent.id.clone(),
153            total_count: self.seqno.load(Ordering::Acquire),
154        };
155
156        self.parent
157            .emitter
158            .emit(&spec::TestStepArtifactImpl::MeasurementSeriesEnd(end))
159            .await?;
160
161        Ok(())
162    }
163
164    /// Ends the measurement series.
165    ///
166    /// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend>
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// # tokio_test::block_on(async {
172    /// # use ocptv::output::*;
173    /// let dut = DutInfo::new("my_dut");
174    /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?;
175    /// let step = run.add_step("step_name").start().await?;
176    ///
177    /// let series = step.add_measurement_series("name").start().await?;
178    /// series.end().await?;
179    ///
180    /// # Ok::<(), OcptvError>(())
181    /// # });
182    /// ```
183    pub async fn end(self) -> Result<(), tv::OcptvError> {
184        self.end_impl().await
185    }
186
187    /// Adds a measurement element to the measurement series.
188    ///
189    /// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement>
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// # tokio_test::block_on(async {
195    /// # use ocptv::output::*;
196    /// let dut = DutInfo::new("my_dut");
197    /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?;
198    /// let step = run.add_step("step_name").start().await?;
199    ///
200    /// let series = step.add_measurement_series("name").start().await?;
201    /// series.add_measurement(60).await?;
202    ///
203    /// # Ok::<(), OcptvError>(())
204    /// # });
205    /// ```
206    pub async fn add_measurement<V: Into<tv::Value>>(
207        &self,
208        value: V,
209    ) -> Result<(), tv::OcptvError> {
210        self.add_measurement_detail(MeasurementElementDetail {
211            value: value.into(),
212            ..Default::default()
213        })
214        .await
215    }
216
217    /// Adds a measurement element to the measurement series.
218    /// This method accepts a full set of details for the measurement element.
219    ///
220    /// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement>
221    ///
222    /// # Examples
223    ///
224    /// ```rust
225    /// # tokio_test::block_on(async {
226    /// # use ocptv::output::*;
227    /// let dut = DutInfo::new("my_dut");
228    /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?;
229    /// let step = run.add_step("step_name").start().await?;
230    ///
231    /// let series = step.add_measurement_series("name").start().await?;
232    /// let elem = MeasurementElementDetail::builder(60).add_metadata("key", "value").build();
233    /// series.add_measurement_detail(elem).await?;
234    ///
235    /// # Ok::<(), OcptvError>(())
236    /// # });
237    /// ```
238    pub async fn add_measurement_detail(
239        &self,
240        element: MeasurementElementDetail,
241    ) -> Result<(), tv::OcptvError> {
242        let element = spec::MeasurementSeriesElement {
243            index: self.incr_seqno(),
244            value: element.value,
245            timestamp: element
246                .timestamp
247                .unwrap_or(self.parent.emitter.timestamp_provider().now()),
248            series_id: self.parent.id.clone(),
249            metadata: element.metadata.option(),
250        };
251
252        self.parent
253            .emitter
254            .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement(
255                element,
256            ))
257            .await?;
258
259        Ok(())
260    }
261}
262
263/// TODO: docs
264pub struct ScopedMeasurementSeries {
265    series: Arc<StartedMeasurementSeries>,
266}
267
268impl ScopedMeasurementSeries {
269    delegate! {
270        to self.series {
271            pub async fn add_measurement<V: Into<tv::Value>>(&self, value: V) -> Result<(), tv::OcptvError>;
272            pub async fn add_measurement_detail(
273                &self,
274                element: MeasurementElementDetail,
275            ) -> Result<(), tv::OcptvError>;
276        }
277    }
278}
279
280/// TODO: docs
281#[derive(Default)]
282pub struct MeasurementElementDetail {
283    value: tv::Value,
284    timestamp: Option<chrono::DateTime<chrono_tz::Tz>>,
285
286    metadata: BTreeMap<String, tv::Value>,
287}
288
289impl MeasurementElementDetail {
290    pub fn builder<V: Into<tv::Value>>(value: V) -> MeasurementElementDetailBuilder {
291        MeasurementElementDetailBuilder::new(value.into())
292    }
293}
294
295/// TODO: docs
296#[derive(Default)]
297pub struct MeasurementElementDetailBuilder {
298    value: tv::Value,
299    timestamp: Option<chrono::DateTime<chrono_tz::Tz>>,
300
301    metadata: BTreeMap<String, tv::Value>,
302}
303
304impl MeasurementElementDetailBuilder {
305    fn new(value: tv::Value) -> Self {
306        Self {
307            value,
308            ..Default::default()
309        }
310    }
311
312    pub fn timestamp(mut self, value: chrono::DateTime<chrono_tz::Tz>) -> Self {
313        self.timestamp = Some(value);
314        self
315    }
316
317    pub fn add_metadata<V: Into<tv::Value>>(mut self, key: &str, value: V) -> Self {
318        self.metadata.insert(key.to_string(), value.into());
319        self
320    }
321
322    pub fn build(self) -> MeasurementElementDetail {
323        MeasurementElementDetail {
324            value: self.value,
325            timestamp: self.timestamp,
326            metadata: self.metadata,
327        }
328    }
329}
330
331/// TODO: docs
332#[derive(Clone)]
333pub struct Validator {
334    name: Option<String>,
335    validator_type: spec::ValidatorType,
336    value: tv::Value,
337    metadata: BTreeMap<String, tv::Value>,
338}
339
340impl Validator {
341    pub fn builder<V: Into<tv::Value>>(
342        validator_type: spec::ValidatorType,
343        value: V,
344    ) -> ValidatorBuilder {
345        ValidatorBuilder::new(validator_type, value.into())
346    }
347
348    pub fn to_spec(&self) -> spec::Validator {
349        spec::Validator {
350            name: self.name.clone(),
351            validator_type: self.validator_type.clone(),
352            value: self.value.clone(),
353            metadata: self.metadata.option(),
354        }
355    }
356}
357
358/// TODO: docs
359#[derive(Debug)]
360pub struct ValidatorBuilder {
361    name: Option<String>,
362    validator_type: spec::ValidatorType,
363    value: tv::Value,
364
365    metadata: BTreeMap<String, tv::Value>,
366}
367
368impl ValidatorBuilder {
369    fn new(validator_type: spec::ValidatorType, value: tv::Value) -> Self {
370        ValidatorBuilder {
371            validator_type,
372            value,
373            name: None,
374            metadata: BTreeMap::new(),
375        }
376    }
377
378    pub fn name(mut self, value: &str) -> Self {
379        self.name = Some(value.to_string());
380        self
381    }
382
383    pub fn add_metadata<V: Into<tv::Value>>(mut self, key: &str, value: V) -> Self {
384        self.metadata.insert(key.to_string(), value.into());
385        self
386    }
387
388    pub fn build(self) -> Validator {
389        Validator {
390            name: self.name,
391            validator_type: self.validator_type,
392            value: self.value,
393            metadata: self.metadata,
394        }
395    }
396}
397
398/// This structure represents a Measurement message.
399/// ref: <https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement>
400///
401/// # Examples
402///
403/// ## Create a Measurement object with the `new` method
404///
405/// ```
406/// # use ocptv::output::*;
407/// let measurement = Measurement::new("name", 50);
408/// ```
409///
410/// ## Create a Measurement object with the `builder` method
411///
412/// ```
413/// # use ocptv::output::*;
414/// let mut dut = DutInfo::new("dut0");
415/// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build());
416///
417/// let measurement = Measurement::builder("name", 50)
418///     .add_validator(Validator::builder(ValidatorType::Equal, 30).build())
419///     .add_metadata("key", "value")
420///     .hardware_info(&hw_info)
421///     .subcomponent(Subcomponent::builder("name").build())
422///     .build();
423/// ```
424#[derive(Default)]
425pub struct Measurement {
426    name: String,
427
428    value: tv::Value,
429    unit: Option<String>,
430    validators: Vec<Validator>,
431
432    hardware_info: Option<dut::DutHardwareInfo>,
433    subcomponent: Option<dut::Subcomponent>,
434
435    metadata: BTreeMap<String, tv::Value>,
436}
437
438impl Measurement {
439    /// Builds a new Measurement object.
440    ///
441    /// # Examples
442    ///
443    /// ```
444    /// # use ocptv::output::*;
445    /// let measurement = Measurement::new("name", 50);
446    /// ```
447    pub fn new<V: Into<tv::Value>>(name: &str, value: V) -> Self {
448        Measurement {
449            name: name.to_string(),
450            value: value.into(),
451            ..Default::default()
452        }
453    }
454
455    /// Builds a new Measurement object using [`MeasurementBuilder`].
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// # use ocptv::output::*;
461    ///
462    /// let mut dut = DutInfo::new("dut0");
463    /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build());
464    ///
465    /// let measurement = Measurement::builder("name", 50)
466    ///     .add_validator(Validator::builder(ValidatorType::Equal, 30).build())
467    ///     .add_metadata("key", "value")
468    ///     .hardware_info(&hw_info)
469    ///     .subcomponent(Subcomponent::builder("name").build())
470    ///     .build();
471    /// ```
472    pub fn builder<V: Into<tv::Value>>(name: &str, value: V) -> MeasurementBuilder {
473        MeasurementBuilder::new(name, value.into())
474    }
475
476    /// Creates an artifact from a Measurement object.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// # use ocptv::output::*;
482    /// let measurement = Measurement::new("name", 50);
483    /// let _ = measurement.to_artifact();
484    /// ```
485    pub fn to_artifact(&self) -> spec::Measurement {
486        spec::Measurement {
487            name: self.name.clone(),
488            unit: self.unit.clone(),
489            value: self.value.clone(),
490            validators: self.validators.map_option(Validator::to_spec),
491            hardware_info: self
492                .hardware_info
493                .as_ref()
494                .map(dut::DutHardwareInfo::to_spec),
495            subcomponent: self
496                .subcomponent
497                .as_ref()
498                .map(|subcomponent| subcomponent.to_spec()),
499            metadata: self.metadata.option(),
500        }
501    }
502}
503
504/// This structure builds a [`Measurement`] object.
505///
506/// # Examples
507///
508/// ```
509/// # use ocptv::output::*;
510/// let mut dut = DutInfo::new("dut0");
511/// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build());
512///
513/// let builder = Measurement::builder("name", 50)
514///     .add_validator(Validator::builder(ValidatorType::Equal, 30).build())
515///     .add_metadata("key", "value")
516///     .hardware_info(&hw_info)
517///     .subcomponent(Subcomponent::builder("name").build());
518/// let measurement = builder.build();
519/// ```
520#[derive(Default)]
521pub struct MeasurementBuilder {
522    name: String,
523
524    value: tv::Value,
525    unit: Option<String>,
526    validators: Vec<Validator>,
527
528    hardware_info: Option<dut::DutHardwareInfo>,
529    subcomponent: Option<dut::Subcomponent>,
530
531    metadata: BTreeMap<String, tv::Value>,
532}
533
534impl MeasurementBuilder {
535    fn new(name: &str, value: tv::Value) -> Self {
536        MeasurementBuilder {
537            name: name.to_string(),
538            value,
539            ..Default::default()
540        }
541    }
542
543    /// Add a [`Validator`] to a [`MeasurementBuilder`].
544    ///
545    /// # Examples
546    ///
547    /// ```
548    /// # use ocptv::output::*;
549    /// let builder = Measurement::builder("name", 50)
550    ///     .add_validator(Validator::builder(ValidatorType::Equal, 30).build());
551    /// ```
552    pub fn add_validator(mut self, validator: Validator) -> Self {
553        self.validators.push(validator.clone());
554        self
555    }
556
557    /// Add a [`tv::HardwareInfo`] to a [`MeasurementBuilder`].
558    ///
559    /// # Examples
560    ///
561    /// ```
562    /// # use ocptv::output::*;
563    /// let mut dut = DutInfo::new("dut0");
564    /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build());
565    ///
566    /// let builder = Measurement::builder("name", 50)
567    ///     .hardware_info(&hw_info);
568    /// ```
569    pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self {
570        self.hardware_info = Some(hardware_info.clone());
571        self
572    }
573
574    /// Add a [`tv::Subcomponent`] to a [`MeasurementBuilder`].
575    ///
576    /// # Examples
577    ///
578    /// ```
579    /// # use ocptv::output::*;
580    /// let builder = Measurement::builder("name", 50)
581    ///     .subcomponent(Subcomponent::builder("name").build());
582    /// ```
583    pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self {
584        self.subcomponent = Some(subcomponent);
585        self
586    }
587
588    /// Add custom metadata to a [`MeasurementBuilder`].
589    ///
590    /// # Examples
591    ///
592    /// ```
593    /// # use ocptv::output::*;
594    /// let builder =
595    ///     Measurement::builder("name", 50).add_metadata("key", "value");
596    /// ```
597    pub fn add_metadata<V: Into<tv::Value>>(mut self, key: &str, value: V) -> Self {
598        self.metadata.insert(key.to_string(), value.into());
599        self
600    }
601
602    /// Add measurement unit to a [`MeasurementBuilder`].
603    ///
604    /// # Examples
605    ///
606    /// ```
607    /// # use ocptv::output::*;
608    /// let builder = Measurement::builder("name", 50000).unit("RPM");
609    /// ```
610    pub fn unit(mut self, unit: &str) -> MeasurementBuilder {
611        self.unit = Some(unit.to_string());
612        self
613    }
614
615    /// Builds a [`Measurement`] object from a [`MeasurementBuilder`].
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// # use ocptv::output::*;
621    /// let builder = Measurement::builder("name", 50);
622    /// let measurement = builder.build();
623    /// ```
624    pub fn build(self) -> Measurement {
625        Measurement {
626            name: self.name,
627            value: self.value,
628            unit: self.unit,
629            validators: self.validators,
630            hardware_info: self.hardware_info,
631            subcomponent: self.subcomponent,
632            metadata: self.metadata,
633        }
634    }
635}
636
637/// TODO: docs
638pub struct MeasurementSeriesDetail {
639    // note: this object is crate public and we need access to this field
640    // when making a new series in `StartedTestStep.add_measurement_series*`
641    pub(crate) id: tv::Ident,
642    name: String,
643
644    unit: Option<String>,
645    validators: Vec<Validator>,
646
647    hardware_info: Option<dut::DutHardwareInfo>,
648    subcomponent: Option<dut::Subcomponent>,
649
650    metadata: BTreeMap<String, tv::Value>,
651}
652
653impl MeasurementSeriesDetail {
654    pub fn new(name: &str) -> MeasurementSeriesDetail {
655        MeasurementSeriesDetailBuilder::new(name).build()
656    }
657
658    pub fn builder(name: &str) -> MeasurementSeriesDetailBuilder {
659        MeasurementSeriesDetailBuilder::new(name)
660    }
661}
662
663/// TODO: docs
664#[derive(Default)]
665pub struct MeasurementSeriesDetailBuilder {
666    id: tv::Ident,
667    name: String,
668
669    unit: Option<String>,
670    validators: Vec<Validator>,
671
672    hardware_info: Option<dut::DutHardwareInfo>,
673    subcomponent: Option<dut::Subcomponent>,
674
675    metadata: BTreeMap<String, tv::Value>,
676}
677
678impl MeasurementSeriesDetailBuilder {
679    fn new(name: &str) -> Self {
680        MeasurementSeriesDetailBuilder {
681            id: Ident::Auto,
682            name: name.to_string(),
683            ..Default::default()
684        }
685    }
686
687    pub fn id(mut self, id: tv::Ident) -> Self {
688        self.id = id;
689        self
690    }
691
692    pub fn unit(mut self, unit: &str) -> Self {
693        self.unit = Some(unit.to_string());
694        self
695    }
696
697    pub fn add_validator(mut self, validator: Validator) -> Self {
698        self.validators.push(validator);
699        self
700    }
701
702    pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self {
703        self.hardware_info = Some(hardware_info.clone());
704        self
705    }
706
707    pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self {
708        self.subcomponent = Some(subcomponent);
709        self
710    }
711
712    pub fn add_metadata<V: Into<tv::Value>>(mut self, key: &str, value: V) -> Self {
713        self.metadata.insert(key.to_string(), value.into());
714        self
715    }
716
717    pub fn build(self) -> MeasurementSeriesDetail {
718        MeasurementSeriesDetail {
719            id: self.id,
720            name: self.name,
721            unit: self.unit,
722            validators: self.validators,
723            hardware_info: self.hardware_info,
724            subcomponent: self.subcomponent,
725            metadata: self.metadata,
726        }
727    }
728}
729
730#[cfg(test)]
731mod tests {
732    use super::*;
733    use crate::output as tv;
734    use crate::spec;
735    use maplit::{btreemap, convert_args};
736    use tv::dut::*;
737    use tv::ValidatorType;
738
739    use anyhow::{bail, Result};
740
741    #[test]
742    fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> {
743        let name = "name".to_owned();
744        let value = tv::Value::from(50);
745        let measurement = Measurement::new(&name, value.clone());
746
747        let artifact = measurement.to_artifact();
748        assert_eq!(
749            artifact,
750            spec::Measurement {
751                name: name.to_string(),
752                unit: None,
753                value,
754                validators: None,
755                hardware_info: None,
756                subcomponent: None,
757                metadata: None,
758            }
759        );
760
761        Ok(())
762    }
763
764    #[test]
765    fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> {
766        let mut dut = DutInfo::new("dut0");
767
768        let name = "name".to_owned();
769        let value = tv::Value::from(50000);
770        let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build());
771        let validator = Validator::builder(spec::ValidatorType::Equal, 30).build();
772
773        let meta_key = "key";
774        let meta_value = tv::Value::from("value");
775        let metadata = convert_args!(btreemap!(
776            meta_key => meta_value.clone(),
777        ));
778
779        let subcomponent = Subcomponent::builder("name").build();
780
781        let unit = "RPM";
782        let measurement = Measurement::builder(&name, value.clone())
783            .unit(unit)
784            .add_validator(validator.clone())
785            .add_validator(validator.clone())
786            .hardware_info(&hw_info)
787            .subcomponent(subcomponent.clone())
788            .add_metadata(meta_key, meta_value.clone())
789            .build();
790
791        let artifact = measurement.to_artifact();
792        assert_eq!(
793            artifact,
794            spec::Measurement {
795                name,
796                value,
797                unit: Some(unit.to_string()),
798                validators: Some(vec![validator.to_spec(), validator.to_spec()]),
799                hardware_info: Some(hw_info.to_spec()),
800                subcomponent: Some(subcomponent.to_spec()),
801                metadata: Some(metadata),
802            }
803        );
804
805        Ok(())
806    }
807
808    #[test]
809    fn test_validator() -> Result<()> {
810        let validator = Validator::builder(ValidatorType::Equal, 30)
811            .name("validator")
812            .add_metadata("key", "value")
813            .add_metadata("key2", "value2")
814            .build();
815
816        let spec_validator = validator.to_spec();
817
818        assert_eq!(spec_validator.name, Some("validator".to_owned()));
819        assert_eq!(spec_validator.value, 30);
820        assert_eq!(spec_validator.validator_type, ValidatorType::Equal);
821
822        match spec_validator.metadata {
823            Some(m) => {
824                assert_eq!(m["key"], "value");
825                assert_eq!(m["key2"], "value2");
826            }
827            _ => bail!("metadata is none"),
828        }
829
830        Ok(())
831    }
832}