use std::collections::HashMap;
use std::path::PathBuf;
use tldr_core::analysis::clones::{
CloneConfig, CloneFragment, ClonePair, ClonesReport, NormalizationMode,
};
use tldr_core::metrics::cognitive::{
CognitiveReport, CognitiveSummary, FunctionCognitive, ThresholdStatus,
};
use tldr_core::quality::smells::{SmellFinding, SmellType, SmellsReport, SmellsSummary};
use tldr_core::security::secrets::{SecretFinding, SecretsReport, SecretsSummary};
use tldr_core::types::{ClassInfo, FunctionInfo, IntraFileCallGraph, Language, ModuleInfo};
use tldr_core::Severity;
use tldr_cli::output::{
format_clones_text, format_cognitive_text, format_module_info_text, format_secrets_text,
format_smells_text,
};
fn cjk_201_bytes() -> String {
"\u{4e16}".repeat(67)
}
fn cjk_path_long() -> PathBuf {
PathBuf::from("\u{4e16}".repeat(14))
}
fn emoji_path_clones_tail_misaligned() -> PathBuf {
PathBuf::from("\u{1f600}".repeat(11))
}
#[test]
fn cli_cognitive_text_does_not_panic_on_cjk_function_name() {
let report = CognitiveReport {
functions: vec![FunctionCognitive {
name: cjk_201_bytes(),
file: "src/lib.rs".to_string(),
line: 1,
cognitive: 1,
cyclomatic: None,
max_nesting: 0,
nesting_penalty: 0,
threshold_status: ThresholdStatus::Ok,
contributors: None,
}],
violations: vec![],
summary: CognitiveSummary::default(),
warnings: vec![],
};
let out = format_cognitive_text(&report);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_smells_text_does_not_panic_on_cjk_smell_name() {
let report = SmellsReport {
smells: vec![SmellFinding {
smell_type: SmellType::LongMethod,
file: PathBuf::from("src/lib.rs"),
name: cjk_201_bytes(),
line: 1,
reason: "test".to_string(),
severity: 1,
suggestion: None,
}],
files_scanned: 1,
by_file: HashMap::new(),
summary: SmellsSummary {
total_smells: 1,
by_type: HashMap::new(),
avg_smells_per_file: 1.0,
},
excluded_test_smells: 0,
};
let out = format_smells_text(&report);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_secrets_text_does_not_panic_on_cjk_file_path() {
let report = SecretsReport {
findings: vec![SecretFinding {
file: cjk_path_long(),
line: 1,
column: 1,
pattern: "AWS Access Key".to_string(),
severity: Severity::Critical,
masked_value: "AKIA********".to_string(),
description: "test".to_string(),
line_content: None,
}],
files_scanned: 1,
patterns_checked: 1,
summary: SecretsSummary {
total_findings: 1,
by_severity: HashMap::new(),
by_pattern: HashMap::new(),
},
};
let out = format_secrets_text(&report);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_clones_text_does_not_panic_on_cjk_file_paths() {
let frag_a = CloneFragment {
file: emoji_path_clones_tail_misaligned(),
start_line: 1,
end_line: 10,
tokens: 50,
lines: Some(10),
function: None,
preview: None,
};
let frag_b = CloneFragment {
file: emoji_path_clones_tail_misaligned(),
start_line: 20,
end_line: 30,
tokens: 50,
lines: Some(10),
function: None,
preview: None,
};
let report = ClonesReport {
root: PathBuf::from("/tmp"),
language: "rust".to_string(),
clone_pairs: vec![ClonePair::new(
1,
tldr_core::analysis::CloneType::Type1,
1.0,
frag_a,
frag_b,
)],
clone_classes: vec![],
stats: tldr_core::analysis::clones::CloneStats::default(),
config: CloneConfig {
min_tokens: 25,
min_lines: 5,
similarity_threshold: 0.7,
normalization: NormalizationMode::All,
type_filter: None,
},
};
let out = format_clones_text(&report);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_module_info_text_does_not_panic_on_cjk_module_docstring() {
let info = ModuleInfo {
file_path: PathBuf::from("test.py"),
language: Language::Python,
docstring: Some("\u{4e16}".repeat(30)),
imports: vec![],
functions: vec![],
classes: vec![],
constants: vec![],
call_graph: IntraFileCallGraph::default(),
};
let out = format_module_info_text(&info);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_module_info_text_does_not_panic_on_cjk_class_docstring() {
let info = ModuleInfo {
file_path: PathBuf::from("test.py"),
language: Language::Python,
docstring: None,
imports: vec![],
functions: vec![],
classes: vec![ClassInfo {
name: "Foo".to_string(),
bases: vec![],
docstring: Some("\u{4e16}".repeat(30)),
methods: vec![],
fields: vec![],
decorators: vec![],
line_number: 1,
}],
constants: vec![],
call_graph: IntraFileCallGraph::default(),
};
let out = format_module_info_text(&info);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn cli_module_info_text_does_not_panic_on_emoji_function_docstring() {
let info = ModuleInfo {
file_path: PathBuf::from("test.py"),
language: Language::Python,
docstring: None,
imports: vec![],
functions: vec![FunctionInfo {
name: "foo".to_string(),
params: vec![],
return_type: None,
docstring: Some("\u{1f600}".repeat(16)),
is_method: false,
is_async: false,
decorators: vec![],
line_number: 1,
}],
classes: vec![],
constants: vec![],
call_graph: IntraFileCallGraph::default(),
};
let out = format_module_info_text(&info);
assert!(
std::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8"
);
}
#[test]
fn truncate_helper_returns_valid_utf8_for_cjk_input() {
let s = "\u{4e16}".repeat(67);
let out = tldr_core::util::truncate_at_char_boundary(&s, 197);
assert!(s.is_char_boundary(out.len()));
assert_eq!(out.len(), 195);
}