#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
mod api;
mod bridge;
mod categorize;
mod cli;
mod compile;
mod config;
mod console_format;
mod docker;
mod download;
mod error_extract;
mod git;
mod manifest;
mod metadata;
mod report;
mod runner;
mod types;
mod ui;
mod version;
use std::fs;
use std::path::PathBuf;
use types::*;
fn main() {
env_logger::init();
let args = cli::CliArgs::parse_args();
if args.docker {
let original_args: Vec<String> = std::env::args().skip(1).collect();
match docker::run_in_docker(&original_args) {
Ok(status) => {
std::process::exit(status.code().unwrap_or(1));
}
Err(e) => {
ui::print_error(&e);
std::process::exit(1);
}
}
}
if let Err(e) = args.validate() {
ui::print_error(&e);
std::process::exit(1);
}
if args.clean {
let staging_dir = args.get_staging_dir();
if staging_dir.exists() {
match fs::remove_dir_all(&staging_dir) {
Ok(_) => {
println!("Cleaned staging directory: {}", staging_dir.display());
}
Err(e) => {
eprintln!("Warning: Failed to clean staging directory: {}", e);
}
}
}
}
if let Some(width) = args.console_width {
console_format::set_console_width(width);
}
let report_dir = PathBuf::from("copter-report");
if let Err(e) = fs::create_dir_all(&report_dir) {
eprintln!("Warning: Failed to create report directory: {}", e);
}
let gitignore_path = PathBuf::from(".gitignore");
if gitignore_path.exists()
&& let Ok(content) = fs::read_to_string(&gitignore_path)
&& !content.lines().any(|line| line.trim() == "copter-report" || line.trim() == "copter-report/")
{
let entry = if content.ends_with('\n') { "copter-report/\n" } else { "\ncopter-report/\n" };
let _ = fs::OpenOptions::new().append(true).open(&gitignore_path).and_then(|mut f| {
use std::io::Write;
f.write_all(entry.as_bytes())
});
}
let matrix = match config::build_test_matrix(&args) {
Ok(m) => m,
Err(e) => {
ui::print_error(&format!("Configuration error: {}", e));
std::process::exit(1);
}
};
let version_strs: Vec<String> = matrix.base_versions.iter().map(|v| v.crate_ref.version.display()).collect();
let display_version = version_strs.first().map(|s| s.as_str()).unwrap_or("unknown");
let force_versions = matrix.base_versions.iter().any(|v| v.override_mode == OverrideMode::Force);
let simple_mode = args.simple;
let base_crate = matrix.base_crate.clone();
if simple_mode {
let dependent_names: Vec<String> = matrix
.dependents
.iter()
.map(|d| format!("{}:{}", d.crate_ref.name, d.crate_ref.version.display()))
.collect();
report::print_simple_header(&matrix.base_crate, display_version, &dependent_names, &version_strs);
} else {
report::init_table_widths(&version_strs, display_version, force_versions);
let test_plan = format_test_plan_string(&matrix);
let this_path = matrix.base_versions.iter().find_map(|v| match &v.crate_ref.source {
CrateSource::Local { path } => Some(path.display().to_string()),
_ => None,
});
report::print_table_header(
&matrix.base_crate,
display_version,
matrix.dependents.len(),
Some(&test_plan),
this_path.as_deref(),
);
}
let mut offered_rows = Vec::new();
let mut prev_dependent: Option<String> = None;
let mut prev_error: Option<String> = None;
let report_dir_clone = report_dir.clone();
let staging_dir = matrix.staging_dir.clone();
let mut current_dependent_results = report::DependentResults::default();
let _test_results = match runner::run_tests(matrix.clone(), |result| {
let row = bridge::test_result_to_offered_row(result);
if simple_mode {
if current_dependent_results.dependent_name != row.primary.dependent_name
|| current_dependent_results.dependent_version != row.primary.dependent_version
{
if !current_dependent_results.dependent_name.is_empty() {
report::print_simple_dependent_result(¤t_dependent_results, &base_crate, &report_dir_clone);
}
current_dependent_results = report::DependentResults {
dependent_name: row.primary.dependent_name.clone(),
dependent_version: row.primary.dependent_version.clone(),
baseline: None,
offered_versions: Vec::new(),
};
}
if row.offered.is_none() {
current_dependent_results.baseline = Some(row.clone());
} else {
current_dependent_results.offered_versions.push(row.clone());
}
} else {
if let Some(ref prev) = prev_dependent
&& *prev != row.primary.dependent_name
{
report::print_separator_line();
}
let is_last = false;
report::print_offered_row(&row, is_last, prev_error.as_deref(), args.error_lines);
}
if !result.execution.is_success() {
report::write_failure_log(&report_dir_clone, &staging_dir, result);
}
prev_error = report::extract_error_text(&row);
prev_dependent = Some(row.primary.dependent_name.clone());
offered_rows.push(row);
}) {
Ok(results) => results,
Err(e) => {
ui::print_error(&format!("Test execution failed: {}", e));
std::process::exit(1);
}
};
if simple_mode && !current_dependent_results.dependent_name.is_empty() {
report::print_simple_dependent_result(¤t_dependent_results, &base_crate, &report_dir);
}
let combined_log_path = report::write_combined_log(&report_dir, &offered_rows, &base_crate);
if simple_mode {
report::print_simple_summary(&offered_rows, &report_dir, &base_crate, &combined_log_path);
} else {
report::print_table_footer();
}
generate_non_console_reports(&offered_rows, &args, &matrix, &report_dir, simple_mode);
if args.dependents.is_empty() && args.dependent_paths.is_empty() {
suggest_failed_retest(&offered_rows, &args, &matrix);
}
let summary = report::summarize_offered_rows(&offered_rows);
let exit_code = if summary.regressed > 0 { -2 } else { 0 };
std::process::exit(exit_code);
}
fn print_test_plan(matrix: &TestMatrix, args: &cli::CliArgs) {
let deps_display: Vec<String> = matrix
.dependents
.iter()
.take(5)
.map(|d| {
let version = d.crate_ref.version.display();
if version == "latest" { d.crate_ref.name.clone() } else { format!("{}:{}", d.crate_ref.name, version) }
})
.collect();
let more_deps = if matrix.dependents.len() > 5 {
format!(" ... and {} more", matrix.dependents.len() - 5)
} else {
String::new()
};
let mut versions_display = vec!["baseline".to_string()];
for version_spec in &matrix.base_versions {
let version_str = version_spec.crate_ref.version.display();
if version_spec.override_mode == OverrideMode::Force {
versions_display.push(format!("{} [!]", version_str));
} else {
versions_display.push(version_str);
}
}
let test_plan = format!(
" Dependents: {}{}\n Versions: {}",
deps_display.join(", "),
more_deps,
versions_display.join(", ")
);
let this_path = matrix.base_versions.iter().find_map(|v| match &v.crate_ref.source {
CrateSource::Local { path } => Some(path.display().to_string()),
_ => None,
});
println!("Testing {} reverse dependencies of {}", matrix.dependents.len(), matrix.base_crate);
println!("{}", test_plan);
if let Some(path) = this_path {
println!(" this = {} (your work-in-progress version)", path);
}
println!();
}
fn generate_non_console_reports(
rows: &[OfferedRow],
_args: &cli::CliArgs,
matrix: &TestMatrix,
report_dir: &std::path::Path,
simple_mode: bool,
) {
let markdown_path = report_dir.join("report.md");
let test_plan = format_test_plan_string(matrix);
let this_path = matrix.base_versions.iter().find_map(|v| match &v.crate_ref.source {
CrateSource::Local { path } => Some(path.display().to_string()),
_ => None,
});
if let Err(e) = report::export_markdown_table_report(
rows,
&markdown_path,
&matrix.base_crate,
&matrix.base_versions.first().map(|v| v.crate_ref.version.display()).unwrap_or_else(|| "unknown".to_string()),
matrix.dependents.len(),
Some(&test_plan),
this_path.as_deref(),
) {
eprintln!("Warning: Failed to save markdown report: {}", e);
}
let json_path = report_dir.join("report.json");
if let Err(e) = report::export_json_report(
rows,
&json_path,
&matrix.base_crate,
&matrix.base_versions.first().map(|v| v.crate_ref.version.display()).unwrap_or_else(|| "unknown".to_string()),
matrix.dependents.len(),
) {
eprintln!("Warning: Failed to save JSON report: {}", e);
}
if !simple_mode {
let comparison_stats = report::generate_comparison_table(rows);
report::print_comparison_table(&comparison_stats);
}
if !simple_mode {
let compat_report = report::build_compatibility_report(rows, &matrix.base_crate);
report::print_compatibility_report(&compat_report, report_dir);
}
}
fn format_test_plan_string(matrix: &TestMatrix) -> String {
let deps_display: Vec<String> = matrix
.dependents
.iter()
.take(5)
.map(|d| {
let version = d.crate_ref.version.display();
if version == "latest" { d.crate_ref.name.clone() } else { format!("{}:{}", d.crate_ref.name, version) }
})
.collect();
let more_deps = if matrix.dependents.len() > 5 {
format!(" ... and {} more", matrix.dependents.len() - 5)
} else {
String::new()
};
let mut versions_display = vec!["baseline".to_string()];
for version_spec in &matrix.base_versions {
let version_str = version_spec.crate_ref.version.display();
if version_spec.override_mode == OverrideMode::Force {
versions_display.push(format!("{} [!]", version_str));
} else {
versions_display.push(version_str);
}
}
format!(" Dependents: {}{}\n Versions: {}", deps_display.join(", "), more_deps, versions_display.join(", "))
}
fn suggest_failed_retest(rows: &[OfferedRow], args: &cli::CliArgs, matrix: &TestMatrix) {
let mut failed_dependents: std::collections::HashSet<String> = std::collections::HashSet::new();
for row in rows {
let failed = match row.baseline_passed {
Some(true) => !row.test.all_passed(), Some(false) => true, None => !row.test.all_passed(), };
if failed {
failed_dependents.insert(row.primary.dependent_name.clone());
}
}
if !failed_dependents.is_empty() && failed_dependents.len() < matrix.dependents.len() {
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("💡 To re-test only the {} failed dependent(s):", failed_dependents.len());
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let mut cmd = String::from("cargo-copter");
if let Some(ref path) = args.path {
cmd.push_str(&format!(" --path {}", path.display()));
} else if let Some(ref crate_name) = args.crate_name {
cmd.push_str(&format!(" --crate {}", crate_name));
}
if !args.test_versions.is_empty() {
cmd.push_str(" --test-versions");
for v in &args.test_versions {
cmd.push_str(&format!(" {}", v));
}
}
if !args.force_versions.is_empty() {
cmd.push_str(" --force-versions");
for v in &args.force_versions {
cmd.push_str(&format!(" {}", v));
}
}
cmd.push_str(" --dependents");
let mut sorted_failed: Vec<_> = failed_dependents.iter().collect();
sorted_failed.sort();
for dep in sorted_failed {
cmd.push_str(&format!(" {}", dep));
}
if args.skip_normal_testing {
cmd.push_str(" --skip-normal-testing");
}
if args.error_lines != 10 {
cmd.push_str(&format!(" --error-lines {}", args.error_lines));
}
println!(" {}\n", cmd);
}
}