use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TestOutcome {
Passed,
Failed,
Skipped,
Error,
Xfail,
Xpass,
}
impl From<&str> for TestOutcome {
fn from(s: &str) -> Self {
match s {
"passed" => TestOutcome::Passed,
"failed" => TestOutcome::Failed,
"skipped" => TestOutcome::Skipped,
"error" => TestOutcome::Error,
"xfail" => TestOutcome::Xfail,
"xpass" => TestOutcome::Xpass,
_ => TestOutcome::Error,
}
}
}
impl From<TestOutcome> for String {
fn from(outcome: TestOutcome) -> Self {
match outcome {
TestOutcome::Passed => "passed".to_string(),
TestOutcome::Failed => "failed".to_string(),
TestOutcome::Skipped => "skipped".to_string(),
TestOutcome::Error => "error".to_string(),
TestOutcome::Xfail => "xfail".to_string(),
TestOutcome::Xpass => "xpass".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TestNode {
pub node_id: String,
pub file_path: String,
pub name: String,
pub class_name: Option<String>,
pub line_number: u32,
pub markers: Vec<String>,
pub skip: bool,
pub xfail: bool,
}
impl From<rpytest_core::protocol::TestNodeInfo> for TestNode {
fn from(info: rpytest_core::protocol::TestNodeInfo) -> Self {
TestNode {
node_id: info.node_id,
file_path: info.file_path,
name: info.name,
class_name: info.class_name,
line_number: info.lineno.unwrap_or(0),
markers: info.markers,
skip: info.skip,
xfail: info.xfail,
}
}
}
impl From<TestNode> for rpytest_core::protocol::TestNodeInfo {
fn from(node: TestNode) -> Self {
rpytest_core::protocol::TestNodeInfo {
node_id: node.node_id,
file_path: node.file_path,
lineno: Some(node.line_number),
name: node.name,
class_name: node.class_name,
markers: node.markers,
skip: node.skip,
xfail: node.xfail,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TestResult {
pub node_id: String,
pub outcome: TestOutcome,
pub duration_ms: u64,
pub message: Option<String>,
pub stdout: Option<String>,
pub stderr: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RunSummary {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub skipped: usize,
pub errors: usize,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NativeTestNode {
pub node_id: String,
pub file_path: String,
pub name: String,
pub class_name: Option<String>,
pub line_number: u32,
pub markers: Vec<String>,
pub is_simple: bool,
pub parameters: Vec<Value>,
pub skip: bool,
pub skip_reason: Option<String>,
pub xfail: bool,
pub xfail_reason: Option<String>,
pub xfail_strict: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ParameterizedTestNode {
pub param_names: Vec<String>,
pub param_values: Vec<(Vec<String>, Option<String>, Vec<String>)>,
pub test_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RerunConfig {
pub enabled: bool,
pub max_reruns: u32,
pub only_flaky: bool,
pub delay_ms: u32,
}
impl Default for RerunConfig {
fn default() -> Self {
RerunConfig {
enabled: false,
max_reruns: 2,
only_flaky: false,
delay_ms: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RerunResult {
pub node_id: String,
pub original_outcome: TestOutcome,
pub rerun_outcomes: Vec<TestOutcome>,
pub final_outcome: TestOutcome,
pub is_flaky: bool,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FlakinessRecord {
pub node_id: String,
pub outcomes: Vec<String>, pub consecutive_failures: u32,
pub consecutive_passes: u32,
pub flaky_streak: u32,
pub total_runs: u32,
pub last_failure_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum FixtureScope {
Session,
Package,
Module,
Class,
Function,
}
impl From<&str> for FixtureScope {
fn from(s: &str) -> Self {
match s {
"session" => FixtureScope::Session,
"package" => FixtureScope::Package,
"module" => FixtureScope::Module,
"class" => FixtureScope::Class,
_ => FixtureScope::Function,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FixtureState {
pub name: String,
pub scope: FixtureScope,
pub created_at: f64,
pub last_used: f64,
pub use_count: u32,
pub teardown_pending: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct FixtureConfig {
pub enabled: bool,
pub max_age_seconds: f64,
pub teardown_on_conftest_change: bool,
pub scopes_to_reuse: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduledTest {
pub node_id: String,
pub estimated_duration_ms: u64,
pub priority: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ShardConfig {
pub shard_index: u32,
pub total_shards: u32,
pub strategy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ExecutorConfig {
pub workers: Option<u32>,
pub maxfail: Option<u32>,
pub batch_size: usize,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum ExecutionMode {
#[default]
Embedded,
Subprocess,
Pooled,
Auto,
}
impl std::fmt::Display for ExecutionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExecutionMode::Embedded => write!(f, "embedded"),
ExecutionMode::Subprocess => write!(f, "subprocess"),
ExecutionMode::Pooled => write!(f, "pooled"),
ExecutionMode::Auto => write!(f, "auto"),
}
}
}
impl std::str::FromStr for ExecutionMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"embedded" => Ok(ExecutionMode::Embedded),
"subprocess" => Ok(ExecutionMode::Subprocess),
"pooled" => Ok(ExecutionMode::Pooled),
"auto" => Ok(ExecutionMode::Auto),
_ => Err(format!("Invalid execution mode: {}. Use 'embedded', 'subprocess', 'pooled', or 'auto'", s)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonConfig {
pub socket_path: PathBuf,
pub storage_path: PathBuf,
pub python_path: Option<PathBuf>,
pub idle_timeout_secs: u32,
pub max_workers: u32,
pub execution_mode: ExecutionMode,
}
impl Default for DaemonConfig {
fn default() -> Self {
DaemonConfig {
socket_path: PathBuf::from("/tmp/rpytest.sock"),
storage_path: dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("rpytest"),
python_path: None,
idle_timeout_secs: 0,
max_workers: 4,
execution_mode: ExecutionMode::Auto,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_mode_from_str() {
assert_eq!(
"embedded".parse::<ExecutionMode>().unwrap(),
ExecutionMode::Embedded
);
assert_eq!(
"subprocess".parse::<ExecutionMode>().unwrap(),
ExecutionMode::Subprocess
);
assert_eq!(
"auto".parse::<ExecutionMode>().unwrap(),
ExecutionMode::Auto
);
assert_eq!(
"EMBEDDED".parse::<ExecutionMode>().unwrap(),
ExecutionMode::Embedded
);
assert_eq!(
"Auto".parse::<ExecutionMode>().unwrap(),
ExecutionMode::Auto
);
assert!("invalid".parse::<ExecutionMode>().is_err());
}
#[test]
fn test_execution_mode_display() {
assert_eq!(ExecutionMode::Embedded.to_string(), "embedded");
assert_eq!(ExecutionMode::Subprocess.to_string(), "subprocess");
assert_eq!(ExecutionMode::Auto.to_string(), "auto");
}
#[test]
fn test_execution_mode_default() {
assert_eq!(ExecutionMode::default(), ExecutionMode::Embedded);
}
#[test]
fn test_daemon_config_default() {
let config = DaemonConfig::default();
assert_eq!(config.execution_mode, ExecutionMode::Auto);
assert_eq!(config.max_workers, 4);
}
#[test]
fn test_test_outcome_conversions() {
assert_eq!(TestOutcome::from("passed"), TestOutcome::Passed);
assert_eq!(TestOutcome::from("failed"), TestOutcome::Failed);
assert_eq!(TestOutcome::from("skipped"), TestOutcome::Skipped);
assert_eq!(TestOutcome::from("error"), TestOutcome::Error);
assert_eq!(TestOutcome::from("xfail"), TestOutcome::Xfail);
assert_eq!(TestOutcome::from("xpass"), TestOutcome::Xpass);
assert_eq!(TestOutcome::from("unknown"), TestOutcome::Error);
assert_eq!(String::from(TestOutcome::Passed), "passed");
assert_eq!(String::from(TestOutcome::Failed), "failed");
}
}