forge/compare_matrix/
types.rs1use 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}