eggress_testkit/
case_model.rs1use 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}