1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! lang-detect-default-v1 — RED guard for the misleading "(Python)"
//! progress banner that printed BEFORE the path-existence check fired.
//!
//! Pre-fix repro:
//! `tldr structure /tmp/this/does/not/exist`
//! would emit:
//! "Extracting structure from /tmp/this/does/not/exist (Python)..."
//! "Error: Path not found: /tmp/this/does/not/exist"
//! The "(Python)" banner falsely implies the lang detector ran and
//! chose Python, when in fact `Language::from_directory` returned
//! `None` (path doesn't exist → empty walk) and the call site
//! silently fell back to `Python` via `.unwrap_or(Language::Python)`.
//!
//! Fix: validate the path BEFORE language detection and the progress
//! banner in every directory-rooted subcommand that uses
//! `Language::from_directory(...).unwrap_or(Language::Python)`.
//!
//! Validation: no language parenthetical in stderr/stdout, and the
//! "Path not found: <path>" error MUST be present.
//!
//! Subcommands covered (must all be GREEN after fix):
//! - structure
//! - calls
//! - dead
//! - impact
//! - importers
//! - search
use assert_cmd::Command;
const MISSING_PATH: &str = "/tmp/tldr-lang-detect-default-v1-does-not-exist-xyz123";
fn run_tldr(args: &[&str]) -> (i32, String, String) {
let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("tldr"));
let output = cmd.args(args).output().expect("tldr binary missing");
let code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(code, stdout, stderr)
}
/// The only language names that would appear in a `({:?})` or `({})` banner
/// — Debug repr of the `Language` enum or the lowercase `as_str()` form.
const LANG_TOKENS: &[&str] = &[
"(Python)",
"(python)",
"(TypeScript)",
"(typescript)",
"(JavaScript)",
"(javascript)",
"(Rust)",
"(rust)",
"(Go)",
"(go)",
"(Java)",
"(java)",
"(Cpp)",
"(cpp)",
"(C)",
"(c)",
];
fn assert_no_lang_banner(stream: &str, label: &str) {
for tok in LANG_TOKENS {
assert!(
!stream.contains(tok),
"{label} contained misleading lang banner {tok:?}; full output:\n{stream}"
);
}
}
fn assert_path_not_found(stderr: &str) {
assert!(
stderr.contains("Path not found"),
"expected 'Path not found' in stderr, got:\n{stderr}"
);
}
// =============================================================================
// structure (the canonical repro)
// =============================================================================
#[test]
fn structure_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["structure", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}
// =============================================================================
// calls
// =============================================================================
#[test]
fn calls_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["calls", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}
// =============================================================================
// dead
// =============================================================================
#[test]
fn dead_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["dead", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}
// =============================================================================
// impact
// =============================================================================
#[test]
fn impact_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["impact", "some_func", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}
// =============================================================================
// importers
// =============================================================================
#[test]
fn importers_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["importers", "some_module", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}
// =============================================================================
// search
// =============================================================================
#[test]
fn search_missing_path_no_lang_banner() {
let (code, stdout, stderr) = run_tldr(&["search", "needle", MISSING_PATH, "-q"]);
assert_ne!(code, 0, "expected failure on missing path");
assert_no_lang_banner(&stdout, "stdout");
assert_no_lang_banner(&stderr, "stderr");
assert_path_not_found(&stderr);
}