Skip to content

Commit 3dbb145

Browse files
hyperpolymathclaude
andcommitted
test: add 22 type tests + criterion benchmark suite
- types_tests.rs: Language detection (47 langs), family classification, serde roundtrip, WeakPointCategory coverage, AssailReport contract - benches/scan_bench.rs: Criterion benchmarks for language detection, family classification, and self-scan (dogfooding) - Cargo.toml: add criterion dev-dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8b6eb1b commit 3dbb145

3 files changed

Lines changed: 283 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ http = ["ureq"]
4141

4242
[dev-dependencies]
4343
tempfile = "3.8"
44+
criterion = { version = "0.5", features = ["html_reports"] }
45+
46+
[[bench]]
47+
name = "scan_bench"
48+
harness = false
4449

4550
[profile.release]
4651
opt-level = 3

benches/scan_bench.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
//! Benchmarks for panic-attack scan performance.
5+
//!
6+
//! Measures: language detection, pattern matching, full analysis pipeline.
7+
8+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
9+
use panic_attack::types::Language;
10+
11+
/// Benchmark language detection from file extension
12+
fn bench_language_detect(c: &mut Criterion) {
13+
let extensions = vec![
14+
"main.rs", "lib.rs", "app.py", "index.js", "server.ex",
15+
"types.idr", "Main.hs", "config.ncl", "build.zig", "test.gleam",
16+
"script.sh", "model.jl", "style.css", "unknown.xyz",
17+
"Component.res", "parser.ml", "proof.lean", "rules.lgt",
18+
];
19+
20+
c.bench_function("language_detect_18_files", |b| {
21+
b.iter(|| {
22+
for ext in &extensions {
23+
black_box(Language::detect(ext));
24+
}
25+
})
26+
});
27+
}
28+
29+
/// Benchmark language family classification
30+
fn bench_language_family(c: &mut Criterion) {
31+
let languages = vec![
32+
Language::Rust, Language::Elixir, Language::Gleam,
33+
Language::ReScript, Language::Idris, Language::Zig,
34+
Language::Haskell, Language::Python, Language::JavaScript,
35+
Language::Shell, Language::Julia, Language::Nickel,
36+
];
37+
38+
c.bench_function("language_family_12_langs", |b| {
39+
b.iter(|| {
40+
for lang in &languages {
41+
black_box(lang.family());
42+
}
43+
})
44+
});
45+
}
46+
47+
/// Benchmark assail analysis on the panic-attacker source itself (dogfooding)
48+
fn bench_self_scan(c: &mut Criterion) {
49+
// Only run if the src directory exists (it should, we're in the repo)
50+
let src_path = std::path::Path::new("src");
51+
if !src_path.exists() {
52+
return;
53+
}
54+
55+
c.bench_function("assail_self_scan", |b| {
56+
b.iter(|| {
57+
let _ = black_box(panic_attack::assail::analyze("src"));
58+
})
59+
});
60+
}
61+
62+
criterion_group!(benches, bench_language_detect, bench_language_family, bench_self_scan);
63+
criterion_main!(benches);

tests/types_tests.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
//! Unit tests for core types — Language detection, family classification,
5+
//! WeakPointCategory coverage, and serialization contracts.
6+
7+
use panic_attack::types::*;
8+
9+
// ─── Language Detection (47 languages) ────────────────────────────────────
10+
11+
#[test]
12+
fn language_detect_rust() {
13+
assert_eq!(Language::detect("src/main.rs"), Language::Rust);
14+
assert_eq!(Language::detect("lib.rs"), Language::Rust);
15+
}
16+
17+
#[test]
18+
fn language_detect_c_family() {
19+
assert_eq!(Language::detect("foo.c"), Language::C);
20+
assert_eq!(Language::detect("foo.h"), Language::C);
21+
assert_eq!(Language::detect("bar.cpp"), Language::Cpp);
22+
assert_eq!(Language::detect("bar.hpp"), Language::Cpp);
23+
}
24+
25+
#[test]
26+
fn language_detect_beam_family() {
27+
assert_eq!(Language::detect("server.ex"), Language::Elixir);
28+
assert_eq!(Language::detect("test.exs"), Language::Elixir);
29+
assert_eq!(Language::detect("gen.erl"), Language::Erlang);
30+
assert_eq!(Language::detect("app.gleam"), Language::Gleam);
31+
}
32+
33+
#[test]
34+
fn language_detect_ml_family() {
35+
assert_eq!(Language::detect("Component.res"), Language::ReScript);
36+
assert_eq!(Language::detect("types.resi"), Language::ReScript);
37+
assert_eq!(Language::detect("parser.ml"), Language::OCaml);
38+
assert_eq!(Language::detect("sig.mli"), Language::OCaml);
39+
assert_eq!(Language::detect("main.sml"), Language::StandardML);
40+
}
41+
42+
#[test]
43+
fn language_detect_proof_assistants() {
44+
assert_eq!(Language::detect("Types.idr"), Language::Idris);
45+
assert_eq!(Language::detect("proof.lean"), Language::Lean);
46+
assert_eq!(Language::detect("module.agda"), Language::Agda);
47+
}
48+
49+
#[test]
50+
fn language_detect_logic_programming() {
51+
assert_eq!(Language::detect("rules.lgt"), Language::Logtalk);
52+
assert_eq!(Language::detect("facts.pl"), Language::Prolog);
53+
assert_eq!(Language::detect("query.dl"), Language::Datalog);
54+
}
55+
56+
#[test]
57+
fn language_detect_systems_languages() {
58+
assert_eq!(Language::detect("build.zig"), Language::Zig);
59+
assert_eq!(Language::detect("main.adb"), Language::Ada);
60+
assert_eq!(Language::detect("spec.ads"), Language::Ada);
61+
assert_eq!(Language::detect("app.odin"), Language::Odin);
62+
assert_eq!(Language::detect("config.nim"), Language::Nim);
63+
assert_eq!(Language::detect("actor.pony"), Language::Pony);
64+
}
65+
66+
#[test]
67+
fn language_detect_config_languages() {
68+
assert_eq!(Language::detect("config.ncl"), Language::Nickel);
69+
assert_eq!(Language::detect("flake.nix"), Language::Nix);
70+
}
71+
72+
#[test]
73+
fn language_detect_scripting() {
74+
assert_eq!(Language::detect("script.sh"), Language::Shell);
75+
assert_eq!(Language::detect("deploy.bash"), Language::Shell);
76+
assert_eq!(Language::detect("model.jl"), Language::Julia);
77+
assert_eq!(Language::detect("plugin.lua"), Language::Lua);
78+
}
79+
80+
#[test]
81+
fn language_detect_nextgen_dsls() {
82+
assert_eq!(Language::detect("module.ecl"), Language::Eclexia);
83+
assert_eq!(Language::detect("linear.eph"), Language::Ephapax);
84+
assert_eq!(Language::detect("game.bet"), Language::BetLang);
85+
assert_eq!(Language::detect("script.woke"), Language::WokeLang);
86+
assert_eq!(Language::detect("query.vql"), Language::VQL);
87+
assert_eq!(Language::detect("types.aff"), Language::AffineScript);
88+
}
89+
90+
#[test]
91+
fn language_detect_javascript_variants() {
92+
// TS/TSX/JSX all map to JavaScript (ReScript is the replacement)
93+
assert_eq!(Language::detect("app.js"), Language::JavaScript);
94+
assert_eq!(Language::detect("module.mjs"), Language::JavaScript);
95+
assert_eq!(Language::detect("legacy.ts"), Language::JavaScript);
96+
assert_eq!(Language::detect("component.tsx"), Language::JavaScript);
97+
assert_eq!(Language::detect("widget.jsx"), Language::JavaScript);
98+
}
99+
100+
#[test]
101+
fn language_detect_unknown() {
102+
assert_eq!(Language::detect("readme.md"), Language::Unknown);
103+
assert_eq!(Language::detect("data.csv"), Language::Unknown);
104+
assert_eq!(Language::detect("image.png"), Language::Unknown);
105+
assert_eq!(Language::detect("noext"), Language::Unknown);
106+
}
107+
108+
// ─── Language Family Classification ───────────────────────────────────────
109+
110+
#[test]
111+
fn language_family_beam() {
112+
assert_eq!(Language::Elixir.family(), "beam");
113+
assert_eq!(Language::Erlang.family(), "beam");
114+
assert_eq!(Language::Gleam.family(), "beam");
115+
}
116+
117+
#[test]
118+
fn language_family_ml() {
119+
assert_eq!(Language::ReScript.family(), "ml");
120+
assert_eq!(Language::OCaml.family(), "ml");
121+
assert_eq!(Language::StandardML.family(), "ml");
122+
}
123+
124+
#[test]
125+
fn language_family_proof() {
126+
assert_eq!(Language::Idris.family(), "proof");
127+
assert_eq!(Language::Lean.family(), "proof");
128+
assert_eq!(Language::Agda.family(), "proof");
129+
}
130+
131+
#[test]
132+
fn language_family_systems() {
133+
assert_eq!(Language::Zig.family(), "systems");
134+
assert_eq!(Language::Ada.family(), "systems");
135+
assert_eq!(Language::Nim.family(), "systems");
136+
}
137+
138+
#[test]
139+
fn language_family_config() {
140+
assert_eq!(Language::Nickel.family(), "config");
141+
assert_eq!(Language::Nix.family(), "config");
142+
}
143+
144+
// ─── Serialization Contracts ──────────────────────────────────────────────
145+
146+
#[test]
147+
fn language_serializes_lowercase() {
148+
let json = serde_json::to_string(&Language::Rust).unwrap();
149+
assert_eq!(json, "\"rust\"");
150+
151+
let json = serde_json::to_string(&Language::Elixir).unwrap();
152+
assert_eq!(json, "\"elixir\"");
153+
154+
let json = serde_json::to_string(&Language::ReScript).unwrap();
155+
assert_eq!(json, "\"rescript\"");
156+
}
157+
158+
#[test]
159+
fn language_deserializes_from_lowercase() {
160+
let lang: Language = serde_json::from_str("\"rust\"").unwrap();
161+
assert_eq!(lang, Language::Rust);
162+
163+
let lang: Language = serde_json::from_str("\"idris\"").unwrap();
164+
assert_eq!(lang, Language::Idris);
165+
}
166+
167+
#[test]
168+
fn language_roundtrip_serde() {
169+
let languages = vec![
170+
Language::Rust, Language::Elixir, Language::Gleam,
171+
Language::ReScript, Language::Idris, Language::Zig,
172+
Language::Haskell, Language::Nickel, Language::Ephapax,
173+
];
174+
175+
for lang in languages {
176+
let json = serde_json::to_string(&lang).unwrap();
177+
let deserialized: Language = serde_json::from_str(&json).unwrap();
178+
assert_eq!(lang, deserialized, "Roundtrip failed for {:?}", lang);
179+
}
180+
}
181+
182+
// ─── WeakPointCategory Coverage ───────────────────────────────────────────
183+
184+
#[test]
185+
fn weak_point_category_serializes() {
186+
let json = serde_json::to_string(&WeakPointCategory::UnsafeCode).unwrap();
187+
assert!(!json.is_empty());
188+
189+
let json = serde_json::to_string(&WeakPointCategory::PanicPath).unwrap();
190+
assert!(!json.is_empty());
191+
}
192+
193+
// ─── AssailReport Structure ───────────────────────────────────────────────
194+
195+
#[test]
196+
fn assail_report_serializes_to_json() {
197+
let report = AssailReport {
198+
program_path: std::path::PathBuf::from("test/target"),
199+
language: Language::Rust,
200+
frameworks: vec![],
201+
weak_points: vec![],
202+
statistics: Default::default(),
203+
file_statistics: vec![],
204+
recommended_attacks: vec![],
205+
dependency_graph: Default::default(),
206+
taint_matrix: Default::default(),
207+
migration_metrics: None,
208+
};
209+
210+
let json = serde_json::to_string(&report).unwrap();
211+
assert!(json.contains("\"language\":\"rust\""));
212+
213+
// Verify it can be deserialized back
214+
let _: AssailReport = serde_json::from_str(&json).unwrap();
215+
}

0 commit comments

Comments
 (0)