Skip to main content

forge/compare_matrix/
types.rs

1use clap::ValueEnum;
2use serde::Serialize;
3use wowlab_types::constants::HUNDRED;
4
5#[derive(Clone, Copy, Debug, Default, ValueEnum)]
6pub(crate) enum MatrixFormat {
7    #[default]
8    Table,
9    Json,
10    Tsv,
11}
12
13#[derive(Clone, Debug, Serialize)]
14pub(super) struct ComparisonRow {
15    pub(super) spec: String,
16    pub(super) status: ComparisonStatus,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub(super) wowlab_dps: Option<f64>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub(super) simc_dps: Option<f64>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub(super) delta_dps: Option<f64>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub(super) delta_pct: Option<f64>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub(super) error: Option<String>,
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
30#[serde(rename_all = "snake_case")]
31pub(super) enum ComparisonStatus {
32    Ok,
33    Failed,
34}
35
36impl ComparisonRow {
37    pub(super) fn success(spec: String, wowlab_dps: f64, simc_dps: f64) -> Self {
38        let delta_dps = wowlab_dps - simc_dps;
39        let delta_pct = (simc_dps > 0.0).then_some(delta_dps / simc_dps * HUNDRED);
40
41        Self {
42            spec,
43            status: ComparisonStatus::Ok,
44            wowlab_dps: Some(wowlab_dps),
45            simc_dps: Some(simc_dps),
46            delta_dps: Some(delta_dps),
47            delta_pct,
48            error: None,
49        }
50    }
51
52    pub(super) fn failed(spec: String, error: String) -> Self {
53        Self {
54            spec,
55            status: ComparisonStatus::Failed,
56            wowlab_dps: None,
57            simc_dps: None,
58            delta_dps: None,
59            delta_pct: None,
60            error: Some(error),
61        }
62    }
63
64    pub(super) fn absolute_delta_pct(&self) -> Option<f64> {
65        self.delta_pct.map(f64::abs)
66    }
67}
68
69#[derive(Debug, Serialize)]
70pub(super) struct ComparisonMatrix {
71    pub(super) duration_secs: u32,
72    pub(super) iterations: u32,
73    pub(super) rows: Vec<ComparisonRow>,
74}
75
76#[cfg(test)]
77mod tests {
78    use googletest::{Result as GtestResult, prelude::*};
79
80    use super::*;
81
82    #[gtest]
83    fn success_calculates_signed_and_absolute_parity_deltas() -> GtestResult<()> {
84        let row = ComparisonRow::success("fire_mage".to_string(), 80.0, 100.0);
85
86        verify_that!(row.delta_dps, some(eq(-20.0)))?;
87        verify_that!(row.delta_pct, some(eq(-20.0)))?;
88        verify_that!(row.absolute_delta_pct(), some(eq(20.0)))?;
89
90        verify_that!(row.status, eq(ComparisonStatus::Ok))
91    }
92
93    #[gtest]
94    fn zero_simc_dps_has_no_percentage_delta() -> GtestResult<()> {
95        let row = ComparisonRow::success("empty_spec".to_string(), 10.0, 0.0);
96
97        verify_that!(row.delta_dps, some(eq(10.0)))?;
98
99        verify_that!(row.delta_pct, none())
100    }
101
102    #[gtest]
103    fn matrix_json_preserves_the_run_parameter_fields() -> GtestResult<()> {
104        let matrix = ComparisonMatrix {
105            duration_secs: 45,
106            iterations: 17,
107            rows: Vec::new(),
108        };
109        let expected = serde_json::json!({
110            "duration_secs": 45,
111            "iterations": 17,
112            "rows": [],
113        });
114
115        verify_that!(serde_json::to_value(matrix).or_fail()?, eq(&expected))
116    }
117}