Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 102 additions & 10 deletions src/cmds/python/pytest_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,28 @@ pub fn run(args: &[String], verbose: u8) -> Result<()> {
c
};

// Force short traceback and quiet mode for compact output
let has_tb_flag = args.iter().any(|a| a.starts_with("--tb"));
let has_quiet_flag = args.iter().any(|a| a == "-q" || a == "--quiet");
// --collect-only shows test tree, not test results — skip -q/--tb injection
let collect_only = args.iter().any(|a| a == "--collect-only" || a == "--co");

if !has_tb_flag {
cmd.arg("--tb=short");
}
if !has_quiet_flag {
cmd.arg("-q");
if !collect_only {
// Force short traceback and quiet mode for compact output
let has_tb_flag = args.iter().any(|a| a.starts_with("--tb"));
let has_quiet_flag = args.iter().any(|a| a == "-q" || a == "--quiet");

if !has_tb_flag {
cmd.arg("--tb=short");
}
if !has_quiet_flag {
cmd.arg("-q");
}
}

for arg in args {
cmd.arg(arg);
}

if verbose > 0 {
eprintln!("Running: pytest --tb=short -q {}", args.join(" "));
eprintln!("Running: {:?}", cmd);
}

let output = cmd
Expand All @@ -52,7 +57,11 @@ pub fn run(args: &[String], verbose: u8) -> Result<()> {
let stderr = String::from_utf8_lossy(&output.stderr);
let raw = format!("{}\n{}", stdout, stderr);

let filtered = filter_pytest_output(&stdout);
let filtered = if collect_only {
filter_collect_only_output(&stdout)
} else {
filter_pytest_output(&stdout)
};

let exit_code = output
.status
Expand Down Expand Up @@ -164,6 +173,52 @@ fn filter_pytest_output(output: &str) -> String {
build_pytest_summary(&summary_line, &test_files, &failures)
}

/// Parse pytest --collect-only output, preserving the test tree
fn filter_collect_only_output(output: &str) -> String {
let mut tree_lines: Vec<&str> = Vec::new();
let mut collected_count: Option<usize> = None;

for line in output.lines() {
let trimmed = line.trim();

// Parse "collected N items" line
if trimmed.starts_with("collected ") {
if let Some(n) = trimmed
.strip_prefix("collected ")
.and_then(|s| s.split_whitespace().next())
.and_then(|n| n.parse::<usize>().ok())
{
collected_count = Some(n);
}
continue;
}

// Collect tree lines (indented or not, starting with <)
if trimmed.starts_with('<') {
tree_lines.push(line);
continue;
}
}

let count = collected_count.unwrap_or(0);

if count == 0 && tree_lines.is_empty() {
return "Pytest: 0 tests collected".to_string();
}

let mut result = format!("Pytest: {} tests collected", count);

if !tree_lines.is_empty() {
result.push('\n');
for line in &tree_lines {
result.push_str(line);
result.push('\n');
}
}

result.trim().to_string()
}

fn build_pytest_summary(summary: &str, _test_files: &[String], failures: &[String]) -> String {
// Parse summary line
let (passed, failed, skipped) = parse_summary_line(summary);
Expand Down Expand Up @@ -358,6 +413,43 @@ collected 0 items
assert!(result.contains("No tests collected"));
}

#[test]
fn test_filter_collect_only() {
let output = r#"=== test session starts ===
collected 2 items

<Dir project>
<Package tests>
<Module test_cli.py>
<Function test_default_greeting>
<Function test_name_argument>

=== 2 tests collected in 0.04s ==="#;

let result = filter_collect_only_output(output);
assert!(
result.contains("2 tests collected"),
"Expected '2 tests collected', got: {}",
result
);
assert!(result.contains("<Function test_default_greeting>"));
assert!(result.contains("<Function test_name_argument>"));
assert!(result.contains("<Module test_cli.py>"));
assert!(!result.contains("No tests collected"));
}

#[test]
fn test_filter_collect_only_zero_items() {
let input = r#"=== test session starts ===
platform linux -- Python 3.12.3
collected 0 items

=== no tests ran in 0.01s ==="#;

let result = filter_collect_only_output(input);
assert_eq!(result, "Pytest: 0 tests collected");
}

#[test]
fn test_parse_summary_line() {
assert_eq!(parse_summary_line("=== 5 passed in 0.50s ==="), (5, 0, 0));
Expand Down