Skip to main content

eggress_testkit/
case_model.rs

1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum EvidenceLevel {
5    Unimplemented,
6    ImplementedSynthetic,
7    ImplementedDifferential,
8    ImplementedInterop,
9    Compatible,
10    IntentionalNonParity,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ComparisonCategory {
15    PayloadEquality,
16    StatusCodeEquality,
17    CloseBehaviorEquality,
18    ErrorClassEquality,
19    ExitCodeEquality,
20    StdoutPatternEquality,
21    StderrPatternEquality,
22    NegativeCaseEquivalence,
23    AllowedDivergence,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum CaseResult {
28    Pass,
29    Fail { details: String },
30    Skip { reason: String },
31}
32
33#[derive(Debug, Clone)]
34pub struct PproxyCase {
35    pub id: &'static str,
36    pub feature_id: &'static str,
37    pub pproxy_args: Vec<String>,
38    pub eggress_args: Vec<String>,
39    pub expected_comparisons: Vec<ComparisonCategory>,
40    pub timeout: Duration,
41    pub manifest_evidence: EvidenceLevel,
42}
43
44#[derive(Debug, Clone)]
45pub struct CaseOutcome {
46    pub case_id: String,
47    pub results: Vec<(ComparisonCategory, CaseResult)>,
48    pub pproxy_output: ProcessOutput,
49    pub eggress_output: ProcessOutput,
50}
51
52#[derive(Debug, Clone)]
53pub struct ProcessOutput {
54    pub stdout: Vec<u8>,
55    pub stderr: Vec<u8>,
56    pub exit_code: Option<i32>,
57}
58
59pub fn compare_payloads(expected: &[u8], actual: &[u8]) -> CaseResult {
60    if expected == actual {
61        CaseResult::Pass
62    } else {
63        CaseResult::Fail {
64            details: format!(
65                "payload mismatch: expected {} bytes, got {} bytes",
66                expected.len(),
67                actual.len()
68            ),
69        }
70    }
71}
72
73pub fn compare_exit_codes(expected: Option<i32>, actual: Option<i32>) -> CaseResult {
74    if expected == actual {
75        CaseResult::Pass
76    } else {
77        CaseResult::Fail {
78            details: format!(
79                "exit code mismatch: expected {:?}, got {:?}",
80                expected, actual
81            ),
82        }
83    }
84}
85
86pub fn compare_stderr_pattern(pattern: &str, stderr: &[u8]) -> CaseResult {
87    let stderr_str = String::from_utf8_lossy(stderr);
88    if stderr_str.contains(pattern) {
89        CaseResult::Pass
90    } else {
91        CaseResult::Fail {
92            details: format!(
93                "stderr pattern '{}' not found in output ({} bytes)",
94                pattern,
95                stderr.len()
96            ),
97        }
98    }
99}
100
101pub fn assert_negative_equivalence(
102    pproxy_result: &CaseResult,
103    eggress_result: &CaseResult,
104) -> CaseResult {
105    let pproxy_is_failure = matches!(pproxy_result, CaseResult::Fail { .. });
106    let eggress_is_failure = matches!(eggress_result, CaseResult::Fail { .. });
107
108    if pproxy_is_failure && eggress_is_failure {
109        CaseResult::Pass
110    } else if pproxy_is_failure && !eggress_is_failure {
111        CaseResult::Fail {
112            details: format!(
113                "negative case divergence: pproxy failed ({}) but eggress succeeded",
114                match pproxy_result {
115                    CaseResult::Fail { details } => details.as_str(),
116                    _ => unreachable!(),
117                }
118            ),
119        }
120    } else if !pproxy_is_failure && eggress_is_failure {
121        CaseResult::Fail {
122            details: format!(
123                "negative case divergence: pproxy succeeded but eggress failed ({})",
124                match eggress_result {
125                    CaseResult::Fail { details } => details.as_str(),
126                    _ => unreachable!(),
127                }
128            ),
129        }
130    } else {
131        CaseResult::Pass
132    }
133}