#![cfg_attr(coverage_nightly, coverage(off))]
use super::semantic_search::ClusterMethod;
use crate::cli::handlers::coverage_improve_handler::CoverageImproveOutputFormat;
use crate::cli::{
BigOOutputFormat, ComplexityOutputFormat, ComprehensiveOutputFormat, DagType,
DeadCodeOutputFormat, DeepContextCacheStrategy, DeepContextDagType, DeepContextOutputFormat,
DefectPredictionOutputFormat, DefectsOutputFormat, DuplicateOutputFormat, DuplicateType,
EntropyOutputFormat, EntropySeverity, GraphMetricType, GraphMetricsOutputFormat,
IncrementalCoverageOutputFormat, LintHotspotOutputFormat, MakefileOutputFormat,
NameSimilarityOutputFormat, OutputFormat, ProofAnnotationOutputFormat, PropertyTypeFilter,
ProvabilityOutputFormat, SatdOutputFormat, SatdSeverity, SearchScope, SymbolTableOutputFormat,
SymbolTypeFilter, TdgOutputFormat, VerificationMethodFilter, WasmOutputFormat,
};
#[cfg(feature = "deep-wasm")]
use crate::cli::{DeepWasmFocus, DeepWasmLanguage, DeepWasmOutputFormat};
use crate::models::churn::ChurnOutputFormat;
use clap::Subcommand;
use std::path::PathBuf;
#[derive(Subcommand)]
#[cfg_attr(test, derive(Debug))]
pub enum AnalyzeCommands {
#[command(visible_aliases = &["btn", "hotspot"])]
Bottleneck {
#[arg(short = 'p', long, default_value = ".")]
path: std::path::PathBuf,
#[arg(long, default_value = "30")]
period: u32,
#[arg(long, default_value = "5")]
threshold: usize,
#[arg(short = 'f', long, value_enum, default_value = "table")]
format: OutputFormat,
#[arg(short = 'o', long)]
output: Option<std::path::PathBuf>,
},
#[command(visible_aliases = &["ch"])]
Churn {
#[arg(short = 'p', long, default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(short = 'd', long, default_value_t = 30)]
days: u32,
#[arg(long, value_enum, default_value = "summary")]
format: ChurnOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value_t = 10)]
top_files: usize,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
},
#[command(visible_aliases = &["cx", "complex"])]
Complexity {
#[arg(short = 'p', long, default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, conflicts_with = "include")]
file: Option<PathBuf>,
#[arg(long, value_delimiter = ',', conflicts_with_all = ["file", "include"])]
files: Vec<PathBuf>,
#[arg(long)]
toolchain: Option<String>,
#[arg(long, value_enum, default_value = "summary")]
format: ComplexityOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
max_cyclomatic: Option<u16>,
#[arg(long)]
max_cognitive: Option<u16>,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
watch: bool,
#[arg(long, default_value_t = 10)]
top_files: usize,
#[arg(long)]
fail_on_violation: bool,
#[arg(long, default_value = "300")]
timeout: u64,
#[arg(long)]
ml: bool,
},
#[command(visible_aliases = &["dep", "graph"])]
Dag {
#[arg(long, value_enum, default_value = "full-dependency")]
dag_type: DagType,
#[arg(short = 'p', long, default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
max_depth: Option<usize>,
#[arg(long)]
target_nodes: Option<usize>,
#[arg(long)]
filter_external: bool,
#[arg(long)]
show_complexity: bool,
#[arg(long)]
include_duplicates: bool,
#[arg(long)]
include_dead_code: bool,
#[arg(long)]
enhanced: bool,
},
#[command(name = "dead-code", visible_aliases = &["dead", "dc"])]
DeadCode {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: DeadCodeOutputFormat,
#[arg(long, short = 't')]
top_files: Option<usize>,
#[arg(long, short = 'u')]
include_unreachable: bool,
#[arg(long, default_value = "10")]
min_dead_lines: usize,
#[arg(long)]
include_tests: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
fail_on_violation: bool,
#[arg(long, default_value = "15.0")]
max_percentage: f64,
#[arg(long, default_value = "900")]
timeout: u64,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
#[arg(long, default_value = "8")]
max_depth: usize,
},
#[command(name = "defects", visible_aliases = &["known-defects"])]
Defects {
#[arg(long, short = 'p', default_value = ".")]
path: Option<PathBuf>,
#[arg(long, conflicts_with = "path")]
file: Option<PathBuf>,
#[arg(long)]
severity: Option<String>,
#[arg(long, value_enum, default_value = "text")]
format: DefectsOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
},
#[command(name = "satd", visible_aliases = &["debt", "td", "tech-debt"])]
Satd {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: SatdOutputFormat,
#[arg(long, value_enum)]
severity: Option<SatdSeverity>,
#[arg(long)]
critical_only: bool,
#[arg(long)]
include_tests: bool,
#[arg(long)]
strict: bool,
#[arg(long)]
extended: bool,
#[arg(long)]
evolution: bool,
#[arg(long, default_value_t = 30)]
days: u32,
#[arg(long)]
metrics: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value_t = 10)]
top_files: usize,
#[arg(long)]
fail_on_violation: bool,
#[arg(long, default_value = "60")]
timeout: u64,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
},
#[command(name = "deep-context", visible_aliases = &["context", "ctx", "deep"])]
DeepContext {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value = "markdown")]
format: DeepContextOutputFormat,
#[arg(long)]
full: bool,
#[arg(long, value_delimiter = ',')]
include: Vec<String>,
#[arg(long, value_delimiter = ',')]
exclude: Vec<String>,
#[arg(long, default_value_t = 30)]
period_days: u32,
#[arg(long, value_enum)]
dag_type: Option<DeepContextDagType>,
#[arg(long)]
max_depth: Option<usize>,
#[arg(long = "include-pattern")]
include_patterns: Vec<String>,
#[arg(long = "exclude-pattern")]
exclude_patterns: Vec<String>,
#[arg(long, value_enum)]
cache_strategy: Option<DeepContextCacheStrategy>,
#[arg(long)]
parallel: Option<usize>,
#[arg(long)]
verbose: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
#[command(name = "tdg")]
Tdg {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(short, long, default_value = "1.5")]
threshold: f64,
#[arg(short = 'n', long, default_value = "10")]
top_files: usize,
#[arg(short, long, value_enum, default_value = "table")]
format: TdgOutputFormat,
#[arg(long)]
include_components: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
critical_only: bool,
#[arg(long)]
verbose: bool,
#[arg(long)]
ml: bool,
},
#[command(name = "build-tdg")]
BuildTdg {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long)]
release: bool,
#[arg(long, default_value = "2.0")]
threshold: f64,
#[arg(long)]
fail_on_regression: bool,
#[arg(long)]
tdg_only: bool,
#[arg(long, default_value = "10")]
top_files: usize,
#[arg(short, long, value_enum, default_value = "table")]
format: TdgOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
},
#[command(name = "lint-hotspot")]
LintHotspot {
#[arg(short = 'p', long, default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long)]
file: Option<PathBuf>,
#[arg(short = 'f', long, value_enum, default_value = "summary")]
format: LintHotspotOutputFormat,
#[arg(
long,
default_value_t = crate::cli::handlers::lint_hotspot_handlers::types::DEFAULT_MAX_DENSITY
)]
max_density: f64,
#[arg(long, default_value_t = 0.8)]
min_confidence: f64,
#[arg(long)]
enforce: bool,
#[arg(long)]
dry_run: bool,
#[arg(long)]
enforcement_metadata: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(
long,
default_value = "-W warnings -W clippy::pedantic -W clippy::nursery -W clippy::cargo"
)]
clippy_flags: String,
#[arg(long, default_value = "10")]
top_files: usize,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
},
Makefile {
#[arg(help = "Path to Makefile to analyze")]
path: PathBuf,
#[arg(
long,
value_delimiter = ',',
default_value = "all",
help = "Comma-separated list of rules to apply"
)]
rules: Vec<String>,
#[arg(short = 'f', long, value_enum, default_value = "human")]
format: MakefileOutputFormat,
#[arg(long, help = "Automatically fix issues where possible")]
fix: bool,
#[arg(
long,
default_value = "4.4",
help = "GNU Make version to check compatibility against"
)]
gnu_version: String,
#[arg(long, default_value = "10")]
top_files: usize,
},
Provability {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, value_delimiter = ',')]
functions: Vec<String>,
#[arg(long)]
analysis_depth: Option<usize>,
#[arg(short = 'f', long, value_enum, default_value = "summary")]
format: ProvabilityOutputFormat,
#[arg(long)]
high_confidence_only: bool,
#[arg(long)]
include_evidence: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value = "10")]
top_files: usize,
},
Duplicates {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, default_value = "all")]
detection_type: DuplicateType,
#[arg(long, default_value = "0.85")]
threshold: f32,
#[arg(long, default_value = "5")]
min_lines: usize,
#[arg(long, default_value = "128")]
max_tokens: usize,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: DuplicateOutputFormat,
#[arg(long)]
perf: bool,
#[arg(long)]
include: Option<String>,
#[arg(long)]
exclude: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value = "10")]
top_files: usize,
},
DefectPrediction {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, default_value = "0.5")]
confidence_threshold: f32,
#[arg(long, default_value = "10")]
min_lines: usize,
#[arg(long)]
include_low_confidence: bool,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: DefectPredictionOutputFormat,
#[arg(long)]
high_risk_only: bool,
#[arg(long)]
include_recommendations: bool,
#[arg(long)]
include: Option<String>,
#[arg(long)]
exclude: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
Comprehensive {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, conflicts_with = "files")]
file: Option<PathBuf>,
#[arg(long, value_delimiter = ',', conflicts_with = "file")]
files: Vec<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: ComprehensiveOutputFormat,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_duplicates: bool,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_dead_code: bool,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_defects: bool,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_complexity: bool,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_tdg: bool,
#[arg(long, default_value = "0.5")]
confidence_threshold: f32,
#[arg(long, default_value = "10")]
min_lines: usize,
#[arg(long)]
include: Option<String>,
#[arg(long)]
exclude: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long)]
executive_summary: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
GraphMetrics {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, value_delimiter = ',', default_value = "all")]
metrics: Vec<GraphMetricType>,
#[arg(long, value_delimiter = ',')]
pagerank_seeds: Vec<String>,
#[arg(long, default_value = "0.85")]
damping_factor: f32,
#[arg(long, default_value = "100")]
max_iterations: usize,
#[arg(long, default_value = "0.001")]
convergence_threshold: f64,
#[arg(long)]
export_graphml: bool,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: GraphMetricsOutputFormat,
#[arg(long)]
include: Option<String>,
#[arg(long)]
exclude: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "20")]
top_k: usize,
#[arg(long, default_value = "0.001")]
min_centrality: f64,
},
NameSimilarity {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
query: String,
#[arg(long, default_value = "10")]
top_k: usize,
#[arg(long)]
phonetic: bool,
#[arg(long, value_enum, default_value = "all")]
scope: SearchScope,
#[arg(long, default_value = "0.3")]
threshold: f32,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: NameSimilarityOutputFormat,
#[arg(long)]
include: Option<String>,
#[arg(long)]
exclude: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long)]
fuzzy: bool,
#[arg(long)]
case_sensitive: bool,
},
ProofAnnotations {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: ProofAnnotationOutputFormat,
#[arg(long)]
high_confidence_only: bool,
#[arg(long)]
include_evidence: bool,
#[arg(long, value_enum)]
property_type: Option<PropertyTypeFilter>,
#[arg(long, value_enum)]
verification_method: Option<VerificationMethodFilter>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long)]
clear_cache: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
IncrementalCoverage {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'b', default_value = "main")]
base_branch: String,
#[arg(long, short = 't')]
target_branch: Option<String>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: IncrementalCoverageOutputFormat,
#[arg(long, default_value = "80.0")]
coverage_threshold: f64,
#[arg(long)]
changed_files_only: bool,
#[arg(long)]
detailed: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long)]
cache_dir: Option<PathBuf>,
#[arg(long)]
force_refresh: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
#[command(visible_aliases = &["improve-coverage", "cov-improve"])]
CoverageImprove {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 't', default_value = "95.0")]
target: f64,
#[arg(long, default_value = "10")]
max_iterations: usize,
#[arg(long)]
fast: bool,
#[arg(long, default_value = "80.0")]
mutation_threshold: f64,
#[arg(long)]
focus: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "text")]
format: CoverageImproveOutputFormat,
},
SymbolTable {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: SymbolTableOutputFormat,
#[arg(long, value_enum)]
filter: Option<SymbolTypeFilter>,
#[arg(long)]
query: Option<String>,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
#[arg(long)]
show_unreferenced: bool,
#[arg(long)]
show_references: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
BigO {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: BigOOutputFormat,
#[arg(long, default_value = "50")]
confidence_threshold: u8,
#[arg(long)]
analyze_space: bool,
#[arg(long)]
include: Vec<String>,
#[arg(long)]
exclude: Vec<String>,
#[arg(long)]
high_complexity_only: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
AssemblyScript {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: ComplexityOutputFormat,
#[arg(long)]
wasm_complexity: bool,
#[arg(long)]
memory_analysis: bool,
#[arg(long)]
security: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value = "30")]
timeout: u64,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
WebAssembly {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: ComplexityOutputFormat,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_binary: bool,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
require_equals = true,
default_value = "true",
default_missing_value = "true"
)]
include_text: bool,
#[arg(long)]
memory_analysis: bool,
#[arg(long)]
security: bool,
#[arg(long)]
complexity: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
#[arg(long, default_value = "10")]
top_files: usize,
},
Clippy {
#[arg(long, short = 'p', default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(long, short = 'c', default_value = "high")]
confidence: String,
#[arg(long)]
dry_run: bool,
#[arg(long, value_delimiter = ',')]
fix_codes: Vec<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
perf: bool,
},
Entropy {
#[arg(short = 'p', long, default_value = ".")]
path: PathBuf,
#[arg(long, hide = true)]
project_path: Option<PathBuf>,
#[arg(short = 'f', long, value_enum, default_value = "summary")]
format: EntropyOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value = "medium")]
min_severity: EntropySeverity,
#[arg(long, default_value_t = 20)]
top_violations: usize,
#[arg(long)]
file: Option<PathBuf>,
#[arg(long)]
include_tests: bool,
},
Wasm {
wasm_file: PathBuf,
#[arg(long, short = 'f', value_enum, default_value = "summary")]
format: WasmOutputFormat,
#[arg(long)]
verify: bool,
#[arg(long)]
security: bool,
#[arg(long)]
profile: bool,
#[arg(long)]
baseline: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
verbose: bool,
},
#[cfg(feature = "deep-wasm")]
DeepWasm {
#[arg(short = 'p', long)]
source_path: PathBuf,
#[arg(long)]
wasm_file: Option<PathBuf>,
#[arg(long)]
dwarf_file: Option<PathBuf>,
#[arg(long)]
source_map: Option<PathBuf>,
#[arg(long, value_enum)]
language: Option<DeepWasmLanguage>,
#[arg(long, value_enum, default_value = "full")]
focus: DeepWasmFocus,
#[arg(long, value_enum, default_value = "markdown")]
format: DeepWasmOutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
strict: bool,
#[arg(long)]
include_mir: bool,
#[arg(long)]
include_llvm_ir: bool,
#[arg(long)]
track_memory: bool,
#[arg(long)]
detect_deadlocks: bool,
},
#[cfg(feature = "mutation-testing")]
Mutate {
#[arg(short = 'p', long)]
path: PathBuf,
#[arg(long, value_delimiter = ',')]
operators: Option<Vec<String>>,
#[arg(long)]
ml_predict: bool,
#[arg(long)]
distributed: bool,
#[arg(long, default_value = "4")]
workers: usize,
#[arg(long)]
progress: bool,
#[arg(long)]
min_score: Option<f64>,
#[arg(long)]
ci_learning: bool,
#[arg(long)]
ci_provider: Option<String>,
#[arg(long, default_value = "50")]
auto_train_threshold: usize,
#[arg(long, value_enum, default_value = "json")]
format: OutputFormat,
#[arg(short, long)]
output: Option<PathBuf>,
},
Cluster {
#[arg(long, value_enum)]
method: ClusterMethod,
#[arg(long)]
k: Option<usize>,
#[arg(long)]
language: Option<String>,
#[arg(long, value_enum, default_value = "table")]
format: OutputFormat,
},
Topics {
#[arg(long)]
num_topics: usize,
#[arg(long)]
language: Option<String>,
#[arg(long, value_enum, default_value = "table")]
format: OutputFormat,
},
#[command(visible_aliases = &["model", "mlops"])]
Models {
#[arg(long, default_value = ".")]
path: std::path::PathBuf,
#[arg(long, value_enum, default_value = "table")]
format: OutputFormat,
#[arg(long)]
check: bool,
},
}
pub(crate) async fn run_within_analysis_budget<F, T>(
what: &str,
timeout_secs: u64,
work: F,
) -> anyhow::Result<T>
where
F: std::future::Future<Output = anyhow::Result<T>> + Send + 'static,
T: Send + 'static,
{
let budget = std::time::Duration::from_secs(timeout_secs);
let task = tokio::spawn(work);
let abort = task.abort_handle();
match tokio::time::timeout(budget, task).await {
Ok(joined) => joined.map_err(|e| anyhow::anyhow!("{what} panicked: {e}"))?,
Err(_) => {
abort.abort();
anyhow::bail!(
"{what} timed out after {timeout_secs} seconds — re-run with a larger --timeout \
(--timeout 0 is a zero-length budget, not 'no limit')"
)
}
}
}
#[cfg(test)]
mod analysis_budget_tests {
use super::run_within_analysis_budget;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn work_that_finishes_inside_the_budget_returns_its_value() {
let got: u32 = run_within_analysis_budget("Test analysis", 30, async { Ok(7) })
.await
.expect("work well inside the budget must not be cancelled");
assert_eq!(got, 7);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn work_that_outlives_the_budget_fails_and_names_the_budget() {
let started = std::time::Instant::now();
let err = run_within_analysis_budget("Test analysis", 1, async {
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
Ok(())
})
.await
.expect_err("30s of work under a 1s budget must not report success");
let elapsed = started.elapsed();
assert!(
elapsed < std::time::Duration::from_secs(10),
"the budget must cut the work short, took {elapsed:?}"
);
let msg = err.to_string();
assert!(
msg.contains("timed out after 1 seconds"),
"the error must name the budget that was exceeded, got: {msg}"
);
assert!(
msg.contains("--timeout"),
"the error must name the knob that moves the budget, got: {msg}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_zero_budget_fails_rather_than_meaning_unlimited() {
let err = run_within_analysis_budget("Test analysis", 0, async {
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
Ok(())
})
.await
.expect_err("--timeout 0 must not silently mean 'no limit'");
assert!(
err.to_string().contains("timed out after 0 seconds"),
"got: {err}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_panicking_analysis_is_reported_as_a_panic_not_a_timeout() {
let err = run_within_analysis_budget("Test analysis", 30, async {
panic!("boom");
#[allow(unreachable_code)]
Ok(())
})
.await
.expect_err("a panicking analysis must not be reported as success");
let msg = err.to_string();
assert!(msg.contains("panicked"), "got: {msg}");
assert!(
!msg.contains("timed out"),
"a panic must not be dressed up as a timeout, got: {msg}"
);
}
}
#[cfg(test)]
mod include_flags_are_switches_tests {
use super::AnalyzeCommands;
use clap::Parser;
#[derive(Parser)]
struct Harness {
#[command(subcommand)]
cmd: AnalyzeCommands,
}
fn parse(args: &[&str]) -> AnalyzeCommands {
let argv: Vec<String> = std::iter::once("pmat".to_string())
.chain(args.iter().map(|s| (*s).to_string()))
.collect();
std::thread::Builder::new()
.stack_size(8 * 1024 * 1024)
.spawn(move || {
Harness::try_parse_from(&argv)
.unwrap_or_else(|e| panic!("failed to parse {argv:?}: {e}"))
.cmd
})
.expect("spawn clap parse thread")
.join()
.expect("clap parse thread panicked")
}
fn comprehensive_flags(args: &[&str]) -> (bool, bool, bool, bool, bool) {
match parse(args) {
AnalyzeCommands::Comprehensive {
include_duplicates,
include_dead_code,
include_defects,
include_complexity,
include_tdg,
..
} => (
include_duplicates,
include_dead_code,
include_defects,
include_complexity,
include_tdg,
),
other => panic!("expected Comprehensive, got {other:?}"),
}
}
fn wasm_flags(args: &[&str]) -> (bool, bool) {
match parse(args) {
AnalyzeCommands::WebAssembly {
include_binary,
include_text,
..
} => (include_binary, include_text),
other => panic!("expected WebAssembly, got {other:?}"),
}
}
#[test]
fn comprehensive_include_flags_default_on() {
assert_eq!(
comprehensive_flags(&["comprehensive"]),
(true, true, true, true, true)
);
}
#[test]
fn comprehensive_include_flags_can_be_turned_off() {
assert!(
!comprehensive_flags(&["comprehensive", "--include-tdg=false"]).4,
"--include-tdg=false must disable it"
);
assert!(
!comprehensive_flags(&["comprehensive", "--include-duplicates=false"]).0,
"--include-duplicates=false must disable it"
);
assert_eq!(
comprehensive_flags(&[
"comprehensive",
"--include-dead-code=false",
"--include-defects=false",
"--include-complexity=false",
]),
(true, false, false, false, true)
);
}
#[test]
fn comprehensive_bare_flag_still_means_enabled() {
assert_eq!(
comprehensive_flags(&["comprehensive", "--include-tdg", "--include-duplicates"]),
(true, true, true, true, true)
);
}
#[test]
fn web_assembly_kind_flags_can_select_one_kind() {
assert_eq!(wasm_flags(&["web-assembly"]), (true, true));
assert_eq!(
wasm_flags(&["web-assembly", "--include-binary=false"]),
(false, true),
"--include-binary=false must select .wat only"
);
assert_eq!(
wasm_flags(&["web-assembly", "--include-text=false"]),
(true, false),
"--include-text=false must select .wasm only"
);
}
}