Skip to content

Commit 2053fd9

Browse files
hyperpolymathclaude
andcommitted
test: convert integration test files to panic-free Result-propagating style
Rewrites three test files to Clade A standards from panic-free-tests-and-benches: types_tests.rs — serde tests now return -> Result<(), serde_json::Error> and use ? instead of .unwrap(). Failures report the actual serde error. Array literal replaces vec![] for the roundtrip case (no allocation needed). analyzer_tests.rs — renames create_test_file -> write_test_file and changes its return type to std::io::Result<PathBuf>. All 13 test functions switch to -> Result<(), Box<dyn std::error::Error>> with ? throughout. The two framework-detection tests convert their inline fs::create_dir_all/write calls too. pattern_tests.rs — same helper refactor as analyzer_tests.rs. All 12 tests become Result-returning. No logic changed — only error propagation style. 47 tests total: 0 failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5ea9b74 commit 2053fd9

3 files changed

Lines changed: 206 additions & 188 deletions

File tree

tests/analyzer_tests.rs

Lines changed: 97 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,223 +1,225 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22

3-
//! Unit tests for language-specific analyzers
3+
//! Unit tests for language-specific analyzers.
4+
//!
5+
//! All tests are panic-free: helpers return `Result`, test functions use
6+
//! `-> Result<(), Box<dyn std::error::Error>>` and propagate errors with `?`.
47
58
use panic_attack::assail;
69
use panic_attack::types::*;
710
use std::fs;
811
use tempfile::TempDir;
912

10-
fn create_test_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf {
13+
/// Write `content` to `dir/name` and return the full path.
14+
///
15+
/// Returns an error rather than panicking so test failures report the
16+
/// actual I/O reason instead of a bare panic.
17+
fn write_test_file(dir: &TempDir, name: &str, content: &str) -> std::io::Result<std::path::PathBuf> {
1118
let path = dir.path().join(name);
12-
fs::write(&path, content).unwrap();
13-
path
19+
fs::write(&path, content)?;
20+
Ok(path)
1421
}
1522

1623
#[test]
17-
fn test_rust_analyzer_detects_unsafe() {
18-
let dir = TempDir::new().unwrap();
19-
let content = r#"
24+
fn test_rust_analyzer_detects_unsafe() -> Result<(), Box<dyn std::error::Error>> {
25+
let dir = TempDir::new()?;
26+
let file = write_test_file(&dir, "test.rs", r#"
2027
fn main() {
2128
unsafe {
2229
let x = std::ptr::null::<i32>();
2330
}
2431
unsafe fn dangerous() {}
2532
}
26-
"#;
27-
let file = create_test_file(&dir, "test.rs", content);
28-
let report = assail::analyze(&file).expect("analysis should succeed");
33+
"#)?;
34+
let report = assail::analyze(&file)?;
2935

3036
assert_eq!(report.language, Language::Rust);
3137
assert!(report.statistics.unsafe_blocks >= 2);
3238
assert!(!report.weak_points.is_empty());
39+
Ok(())
3340
}
3441

3542
#[test]
36-
fn test_rust_analyzer_detects_unwraps() {
37-
let dir = TempDir::new().unwrap();
38-
let content = r#"
43+
fn test_rust_analyzer_detects_unwraps() -> Result<(), Box<dyn std::error::Error>> {
44+
let dir = TempDir::new()?;
45+
let file = write_test_file(&dir, "test.rs", r#"
3946
fn main() {
4047
let x = Some(5).unwrap();
4148
let y = Ok::<i32, ()>(10).expect("should work");
4249
let z = vec![1,2,3].get(0).unwrap();
4350
}
44-
"#;
45-
let file = create_test_file(&dir, "test.rs", content);
46-
let report = assail::analyze(&file).expect("analysis should succeed");
51+
"#)?;
52+
let report = assail::analyze(&file)?;
4753

4854
assert!(report.statistics.unwrap_calls >= 3);
55+
Ok(())
4956
}
5057

5158
#[test]
52-
fn test_rust_analyzer_detects_panics() {
53-
let dir = TempDir::new().unwrap();
54-
let content = r#"
59+
fn test_rust_analyzer_detects_panics() -> Result<(), Box<dyn std::error::Error>> {
60+
let dir = TempDir::new()?;
61+
let file = write_test_file(&dir, "test.rs", r#"
5562
fn main() {
5663
panic!("oh no");
5764
unreachable!("never happens");
5865
}
59-
"#;
60-
let file = create_test_file(&dir, "test.rs", content);
61-
let report = assail::analyze(&file).expect("analysis should succeed");
66+
"#)?;
67+
let report = assail::analyze(&file)?;
6268

6369
assert!(report.statistics.panic_sites >= 2);
70+
Ok(())
6471
}
6572

6673
#[test]
67-
fn test_c_analyzer_detects_malloc() {
68-
let dir = TempDir::new().unwrap();
69-
let content = r#"
74+
fn test_c_analyzer_detects_malloc() -> Result<(), Box<dyn std::error::Error>> {
75+
let dir = TempDir::new()?;
76+
let file = write_test_file(&dir, "test.c", r#"
7077
#include <stdlib.h>
7178
7279
int main() {
7380
int* ptr = malloc(sizeof(int) * 100);
7481
int* ptr2 = calloc(50, sizeof(int));
7582
return 0;
7683
}
77-
"#;
78-
let file = create_test_file(&dir, "test.c", content);
79-
let report = assail::analyze(&file).expect("analysis should succeed");
84+
"#)?;
85+
let report = assail::analyze(&file)?;
8086

8187
assert_eq!(report.language, Language::C);
8288
assert!(report.statistics.allocation_sites >= 2);
89+
Ok(())
8390
}
8491

8592
#[test]
86-
fn test_c_analyzer_detects_unchecked_malloc() {
87-
let dir = TempDir::new().unwrap();
88-
let content = r#"
93+
fn test_c_analyzer_detects_unchecked_malloc() -> Result<(), Box<dyn std::error::Error>> {
94+
let dir = TempDir::new()?;
95+
let file = write_test_file(&dir, "test.c", r#"
8996
#include <stdlib.h>
9097
9198
int main() {
9299
int* ptr = malloc(100);
93100
*ptr = 42; // Unchecked!
94101
}
95-
"#;
96-
let file = create_test_file(&dir, "test.c", content);
97-
let report = assail::analyze(&file).expect("analysis should succeed");
102+
"#)?;
103+
let report = assail::analyze(&file)?;
98104

99105
let unchecked = report
100106
.weak_points
101107
.iter()
102108
.any(|wp| matches!(wp.category, WeakPointCategory::UncheckedAllocation));
103109
assert!(unchecked, "Should detect unchecked malloc");
110+
Ok(())
104111
}
105112

106113
#[test]
107-
fn test_go_analyzer_detects_goroutines() {
108-
let dir = TempDir::new().unwrap();
109-
let content = r#"
114+
fn test_go_analyzer_detects_goroutines() -> Result<(), Box<dyn std::error::Error>> {
115+
let dir = TempDir::new()?;
116+
let file = write_test_file(&dir, "test.go", r#"
110117
package main
111118
112119
func main() {
113120
go func() { println("hello") }()
114121
go processData()
115122
go handleRequest()
116123
}
117-
"#;
118-
let file = create_test_file(&dir, "test.go", content);
119-
let report = assail::analyze(&file).expect("analysis should succeed");
124+
"#)?;
125+
let report = assail::analyze(&file)?;
120126

121127
assert_eq!(report.language, Language::Go);
122128
assert!(report.statistics.threading_constructs >= 3);
129+
Ok(())
123130
}
124131

125132
#[test]
126-
fn test_python_analyzer_detects_unbounded_loop() {
127-
let dir = TempDir::new().unwrap();
128-
let content = r#"
133+
fn test_python_analyzer_detects_unbounded_loop() -> Result<(), Box<dyn std::error::Error>> {
134+
let dir = TempDir::new()?;
135+
let file = write_test_file(&dir, "test.py", r#"
129136
def main():
130137
while True:
131138
process()
132-
"#;
133-
let file = create_test_file(&dir, "test.py", content);
134-
let report = assail::analyze(&file).expect("analysis should succeed");
139+
"#)?;
140+
let report = assail::analyze(&file)?;
135141

136142
assert_eq!(report.language, Language::Python);
137143
let unbounded = report
138144
.weak_points
139145
.iter()
140146
.any(|wp| matches!(wp.category, WeakPointCategory::UnboundedLoop));
141147
assert!(unbounded, "Should detect unbounded loop");
148+
Ok(())
142149
}
143150

144151
#[test]
145-
fn test_generic_analyzer_basic_patterns() {
146-
let dir = TempDir::new().unwrap();
147-
let content = r#"
152+
fn test_generic_analyzer_basic_patterns() -> Result<(), Box<dyn std::error::Error>> {
153+
let dir = TempDir::new()?;
154+
let file = write_test_file(&dir, "test.unknown", r#"
148155
// Unknown language
149156
function main() {
150157
let x = alloc(100);
151158
open("file.txt");
152159
thread.start();
153160
}
154-
"#;
155-
let file = create_test_file(&dir, "test.unknown", content);
156-
157-
// Generic analyzer should still work, but language will be Unknown
158-
// and we just check it doesn't crash
161+
"#)?;
162+
// Generic analyzer should still work; we only check it doesn't crash
159163
let _ = assail::analyze(&file);
164+
Ok(())
160165
}
161166

162167
#[test]
163-
fn test_framework_detection_webserver() {
164-
// Framework detection for Rust relies on Cargo.toml, so we create a directory
168+
fn test_framework_detection_webserver() -> Result<(), Box<dyn std::error::Error>> {
169+
// Framework detection for Rust uses Cargo.toml, so we need a directory
165170
// with both a source file and a manifest declaring the dependency.
166-
let dir = TempDir::new().unwrap();
171+
let dir = TempDir::new()?;
167172
let src_dir = dir.path().join("src");
168-
fs::create_dir_all(&src_dir).unwrap();
173+
fs::create_dir_all(&src_dir)?;
169174
fs::write(
170175
src_dir.join("main.rs"),
171176
"use actix_web::{web, App, HttpServer};\nfn main() {}\n",
172-
)
173-
.unwrap();
177+
)?;
174178
fs::write(
175179
dir.path().join("Cargo.toml"),
176180
"[package]\nname = \"test\"\n[dependencies]\nactix-web = \"4\"\n",
177-
)
178-
.unwrap();
179-
let report = assail::analyze(dir.path()).expect("analysis should succeed");
181+
)?;
182+
let report = assail::analyze(dir.path())?;
180183

181184
assert!(
182185
report.frameworks.contains(&Framework::WebServer),
183186
"expected WebServer from Cargo.toml actix-web dep, got {:?}",
184187
report.frameworks
185188
);
189+
Ok(())
186190
}
187191

188192
#[test]
189-
fn test_framework_detection_database() {
190-
let dir = TempDir::new().unwrap();
193+
fn test_framework_detection_database() -> Result<(), Box<dyn std::error::Error>> {
194+
let dir = TempDir::new()?;
191195
let src_dir = dir.path().join("src");
192-
fs::create_dir_all(&src_dir).unwrap();
196+
fs::create_dir_all(&src_dir)?;
193197
fs::write(
194198
src_dir.join("main.rs"),
195199
"use diesel::prelude::*;\nfn main() {}\n",
196-
)
197-
.unwrap();
200+
)?;
198201
fs::write(
199202
dir.path().join("Cargo.toml"),
200203
"[package]\nname = \"test\"\n[dependencies]\ndiesel = \"2\"\n",
201-
)
202-
.unwrap();
203-
let report = assail::analyze(dir.path()).expect("analysis should succeed");
204+
)?;
205+
let report = assail::analyze(dir.path())?;
204206

205207
assert!(
206208
report.frameworks.contains(&Framework::Database),
207209
"expected Database from Cargo.toml diesel dep, got {:?}",
208210
report.frameworks
209211
);
212+
Ok(())
210213
}
211214

212215
#[test]
213-
fn test_todo_in_string_literal_does_not_trigger_unchecked_error() {
214-
// Regression test for 007-lang false positive: parser.rs contained
215-
// 155 `.expect("TODO: handle error")` calls. The old detector did
216-
// `content.matches("TODO").count()` on raw content, so each string
217-
// literal incremented the TODO count. This pattern is common in
218-
// stub code and shouldn't be classified as UncheckedError.
219-
let dir = TempDir::new().unwrap();
220-
let content = r#"
216+
fn test_todo_in_string_literal_does_not_trigger_unchecked_error() -> Result<(), Box<dyn std::error::Error>> {
217+
// Regression: parser.rs had 155 `.expect("TODO: handle error")` calls.
218+
// The old detector counted `content.matches("TODO")` on raw bytes, so each
219+
// string literal incremented the TODO counter. Stub code with `.expect("TODO: …")`
220+
// must not fire UncheckedError.
221+
let dir = TempDir::new()?;
222+
let file = write_test_file(&dir, "stubby.rs", r#"
221223
pub fn parse_stubbed(input: &str) -> String {
222224
let first = input.split(',').next().expect("TODO: handle error");
223225
let second = input.split('.').next().expect("TODO: handle error");
@@ -234,9 +236,8 @@ pub fn parse_stubbed(input: &str) -> String {
234236
first, second, third, fourth, fifth, sixth,
235237
seventh, eighth, ninth, tenth, eleventh)
236238
}
237-
"#;
238-
let file = create_test_file(&dir, "stubby.rs", content);
239-
let report = assail::analyze(&file).expect("analysis should succeed");
239+
"#)?;
240+
let report = assail::analyze(&file)?;
240241

241242
let unchecked: Vec<_> = report
242243
.weak_points
@@ -250,13 +251,14 @@ pub fn parse_stubbed(input: &str) -> String {
250251
UncheckedError markers: got {:?}",
251252
unchecked
252253
);
254+
Ok(())
253255
}
254256

255257
#[test]
256-
fn test_real_todo_comments_still_detected() {
257-
// Sanity: actual `// TODO` comments in the 11+ threshold still fire.
258-
let dir = TempDir::new().unwrap();
259-
let content = r#"
258+
fn test_real_todo_comments_still_detected() -> Result<(), Box<dyn std::error::Error>> {
259+
// Sanity: actual `// TODO` comments above the 11-item threshold must still fire.
260+
let dir = TempDir::new()?;
261+
let file = write_test_file(&dir, "debt.rs", r#"
260262
// TODO: implement proper error handling
261263
// TODO: add tests for edge cases
262264
// TODO: optimise hot path
@@ -270,9 +272,8 @@ fn test_real_todo_comments_still_detected() {
270272
// XXX: this block needs review
271273
// XXX: performance critical but correctness unclear
272274
pub fn stub() -> i32 { 42 }
273-
"#;
274-
let file = create_test_file(&dir, "debt.rs", content);
275-
let report = assail::analyze(&file).expect("analysis should succeed");
275+
"#)?;
276+
let report = assail::analyze(&file)?;
276277

277278
let unchecked: Vec<_> = report
278279
.weak_points
@@ -285,19 +286,19 @@ pub fn stub() -> i32 { 42 }
285286
"real // TODO / // FIXME / // HACK / // XXX comments above \
286287
the threshold should still fire the detector"
287288
);
289+
Ok(())
288290
}
289291

290292
#[test]
291-
fn test_per_file_stats_populated() {
292-
let dir = TempDir::new().unwrap();
293-
let content = r#"
293+
fn test_per_file_stats_populated() -> Result<(), Box<dyn std::error::Error>> {
294+
let dir = TempDir::new()?;
295+
let file = write_test_file(&dir, "test.rs", r#"
294296
fn main() {
295297
let x = Some(5).unwrap();
296298
unsafe { std::ptr::null::<i32>() };
297299
}
298-
"#;
299-
let file = create_test_file(&dir, "test.rs", content);
300-
let report = assail::analyze(&file).expect("analysis should succeed");
300+
"#)?;
301+
let report = assail::analyze(&file)?;
301302

302303
assert!(
303304
!report.file_statistics.is_empty(),
@@ -306,4 +307,5 @@ fn main() {
306307
let stats = &report.file_statistics[0];
307308
assert!(stats.file_path.contains("test.rs"));
308309
assert!(stats.lines > 0);
310+
Ok(())
309311
}

0 commit comments

Comments
 (0)