1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "lowercase")]
10pub enum TestOutcome {
11 Passed,
12 Failed,
13 Skipped,
14 Error,
15 Xfail,
16 Xpass,
17}
18
19impl From<&str> for TestOutcome {
20 fn from(s: &str) -> Self {
21 match s {
22 "passed" => TestOutcome::Passed,
23 "failed" => TestOutcome::Failed,
24 "skipped" => TestOutcome::Skipped,
25 "error" => TestOutcome::Error,
26 "xfail" => TestOutcome::Xfail,
27 "xpass" => TestOutcome::Xpass,
28 _ => TestOutcome::Error,
29 }
30 }
31}
32
33impl From<TestOutcome> for String {
34 fn from(outcome: TestOutcome) -> Self {
35 match outcome {
36 TestOutcome::Passed => "passed".to_string(),
37 TestOutcome::Failed => "failed".to_string(),
38 TestOutcome::Skipped => "skipped".to_string(),
39 TestOutcome::Error => "error".to_string(),
40 TestOutcome::Xfail => "xfail".to_string(),
41 TestOutcome::Xpass => "xpass".to_string(),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48pub struct TestNode {
49 pub node_id: String,
50 pub file_path: String,
51 pub name: String,
52 pub class_name: Option<String>,
53 pub line_number: u32,
54 pub markers: Vec<String>,
55 pub skip: bool,
56 pub xfail: bool,
57}
58
59impl From<rpytest_core::protocol::TestNodeInfo> for TestNode {
60 fn from(info: rpytest_core::protocol::TestNodeInfo) -> Self {
61 TestNode {
62 node_id: info.node_id,
63 file_path: info.file_path,
64 name: info.name,
65 class_name: info.class_name,
66 line_number: info.lineno.unwrap_or(0),
67 markers: info.markers,
68 skip: info.skip,
69 xfail: info.xfail,
70 }
71 }
72}
73
74impl From<TestNode> for rpytest_core::protocol::TestNodeInfo {
75 fn from(node: TestNode) -> Self {
76 rpytest_core::protocol::TestNodeInfo {
77 node_id: node.node_id,
78 file_path: node.file_path,
79 lineno: Some(node.line_number),
80 name: node.name,
81 class_name: node.class_name,
82 markers: node.markers,
83 skip: node.skip,
84 xfail: node.xfail,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct TestResult {
92 pub node_id: String,
93 pub outcome: TestOutcome,
94 pub duration_ms: u64,
95 pub message: Option<String>,
96 pub stdout: Option<String>,
97 pub stderr: Option<String>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102pub struct RunSummary {
103 pub total: usize,
104 pub passed: usize,
105 pub failed: usize,
106 pub skipped: usize,
107 pub errors: usize,
108 pub duration_ms: u64,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
113pub struct NativeTestNode {
114 pub node_id: String,
115 pub file_path: String,
116 pub name: String,
117 pub class_name: Option<String>,
118 pub line_number: u32,
119 pub markers: Vec<String>,
120 pub is_simple: bool,
121 pub parameters: Vec<Value>,
122 pub skip: bool,
123 pub skip_reason: Option<String>,
124 pub xfail: bool,
125 pub xfail_reason: Option<String>,
126 pub xfail_strict: bool,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
131pub struct ParameterizedTestNode {
132 pub param_names: Vec<String>,
134 pub param_values: Vec<(Vec<String>, Option<String>, Vec<String>)>,
140 pub test_id: String,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146pub struct RerunConfig {
147 pub enabled: bool,
148 pub max_reruns: u32,
149 pub only_flaky: bool,
150 pub delay_ms: u32,
151}
152
153impl Default for RerunConfig {
154 fn default() -> Self {
155 RerunConfig {
156 enabled: false,
157 max_reruns: 2,
158 only_flaky: false,
159 delay_ms: 0,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
166pub struct RerunResult {
167 pub node_id: String,
168 pub original_outcome: TestOutcome,
169 pub rerun_outcomes: Vec<TestOutcome>,
170 pub final_outcome: TestOutcome,
171 pub is_flaky: bool,
172 pub message: Option<String>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177pub struct FlakinessRecord {
178 pub node_id: String,
179 pub outcomes: Vec<String>, pub consecutive_failures: u32,
181 pub consecutive_passes: u32,
182 pub flaky_streak: u32,
183 pub total_runs: u32,
184 pub last_failure_message: Option<String>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189#[serde(rename_all = "lowercase")]
190pub enum FixtureScope {
191 Session,
192 Package,
193 Module,
194 Class,
195 Function,
196}
197
198impl From<&str> for FixtureScope {
199 fn from(s: &str) -> Self {
200 match s {
201 "session" => FixtureScope::Session,
202 "package" => FixtureScope::Package,
203 "module" => FixtureScope::Module,
204 "class" => FixtureScope::Class,
205 _ => FixtureScope::Function,
206 }
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212pub struct FixtureState {
213 pub name: String,
214 pub scope: FixtureScope,
215 pub created_at: f64,
216 pub last_used: f64,
217 pub use_count: u32,
218 pub teardown_pending: bool,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
223pub struct FixtureConfig {
224 pub enabled: bool,
225 pub max_age_seconds: f64,
226 pub teardown_on_conftest_change: bool,
227 pub scopes_to_reuse: Vec<String>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct ScheduledTest {
233 pub node_id: String,
234 pub estimated_duration_ms: u64,
235 pub priority: u64,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
240pub struct ShardConfig {
241 pub shard_index: u32,
242 pub total_shards: u32,
243 pub strategy: String,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
248pub struct ExecutorConfig {
249 pub workers: Option<u32>,
250 pub maxfail: Option<u32>,
251 pub batch_size: usize,
252}
253
254#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
256#[serde(rename_all = "lowercase")]
257pub enum ExecutionMode {
258 #[default]
260 Embedded,
261 Subprocess,
263 Pooled,
265 Auto,
267}
268
269impl std::fmt::Display for ExecutionMode {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 match self {
272 ExecutionMode::Embedded => write!(f, "embedded"),
273 ExecutionMode::Subprocess => write!(f, "subprocess"),
274 ExecutionMode::Pooled => write!(f, "pooled"),
275 ExecutionMode::Auto => write!(f, "auto"),
276 }
277 }
278}
279
280impl std::str::FromStr for ExecutionMode {
281 type Err = String;
282
283 fn from_str(s: &str) -> Result<Self, Self::Err> {
284 match s.to_lowercase().as_str() {
285 "embedded" => Ok(ExecutionMode::Embedded),
286 "subprocess" => Ok(ExecutionMode::Subprocess),
287 "pooled" => Ok(ExecutionMode::Pooled),
288 "auto" => Ok(ExecutionMode::Auto),
289 _ => Err(format!("Invalid execution mode: {}. Use 'embedded', 'subprocess', 'pooled', or 'auto'", s)),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct DaemonConfig {
297 pub socket_path: PathBuf,
298 pub storage_path: PathBuf,
299 pub python_path: Option<PathBuf>,
300 pub idle_timeout_secs: u32,
301 pub max_workers: u32,
302 pub execution_mode: ExecutionMode,
304}
305
306impl Default for DaemonConfig {
307 fn default() -> Self {
308 DaemonConfig {
309 socket_path: PathBuf::from("/tmp/rpytest.sock"),
310 storage_path: dirs::data_dir()
311 .unwrap_or_else(|| PathBuf::from("."))
312 .join("rpytest"),
313 python_path: None,
314 idle_timeout_secs: 0,
315 max_workers: 4,
316 execution_mode: ExecutionMode::Auto,
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_execution_mode_from_str() {
327 assert_eq!(
328 "embedded".parse::<ExecutionMode>().unwrap(),
329 ExecutionMode::Embedded
330 );
331 assert_eq!(
332 "subprocess".parse::<ExecutionMode>().unwrap(),
333 ExecutionMode::Subprocess
334 );
335 assert_eq!(
336 "auto".parse::<ExecutionMode>().unwrap(),
337 ExecutionMode::Auto
338 );
339
340 assert_eq!(
342 "EMBEDDED".parse::<ExecutionMode>().unwrap(),
343 ExecutionMode::Embedded
344 );
345 assert_eq!(
346 "Auto".parse::<ExecutionMode>().unwrap(),
347 ExecutionMode::Auto
348 );
349
350 assert!("invalid".parse::<ExecutionMode>().is_err());
352 }
353
354 #[test]
355 fn test_execution_mode_display() {
356 assert_eq!(ExecutionMode::Embedded.to_string(), "embedded");
357 assert_eq!(ExecutionMode::Subprocess.to_string(), "subprocess");
358 assert_eq!(ExecutionMode::Auto.to_string(), "auto");
359 }
360
361 #[test]
362 fn test_execution_mode_default() {
363 assert_eq!(ExecutionMode::default(), ExecutionMode::Embedded);
364 }
365
366 #[test]
367 fn test_daemon_config_default() {
368 let config = DaemonConfig::default();
369 assert_eq!(config.execution_mode, ExecutionMode::Auto);
370 assert_eq!(config.max_workers, 4);
371 }
372
373 #[test]
374 fn test_test_outcome_conversions() {
375 assert_eq!(TestOutcome::from("passed"), TestOutcome::Passed);
376 assert_eq!(TestOutcome::from("failed"), TestOutcome::Failed);
377 assert_eq!(TestOutcome::from("skipped"), TestOutcome::Skipped);
378 assert_eq!(TestOutcome::from("error"), TestOutcome::Error);
379 assert_eq!(TestOutcome::from("xfail"), TestOutcome::Xfail);
380 assert_eq!(TestOutcome::from("xpass"), TestOutcome::Xpass);
381 assert_eq!(TestOutcome::from("unknown"), TestOutcome::Error);
382
383 assert_eq!(String::from(TestOutcome::Passed), "passed");
384 assert_eq!(String::from(TestOutcome::Failed), "failed");
385 }
386}