Skip to content

Commit 3700c14

Browse files
committed
feat(stuff): more tests
1 parent b825275 commit 3700c14

27 files changed

Lines changed: 10035 additions & 75 deletions

src/report/html/renderer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ pub fn generate_html_report(
517517
findings.len()
518518
));
519519

520+
// Create parent directory if it doesn't exist
521+
if let Some(parent) = std::path::Path::new(output_path).parent() {
522+
std::fs::create_dir_all(parent)
523+
.map_err(|e| format!("Failed to create output directory: {}", e))?;
524+
}
525+
520526
fs::write(output_path, html).map_err(|e| format!("Failed to write HTML report: {}", e))?;
521527
Ok(())
522528
}

src/rulesynth/mod.rs

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl<'a> RuleSynthesizer<'a> {
5050

5151
// Parse YAML rules from response
5252
let yaml_content = response.content;
53-
let rules = self.parse_yaml_rules(&yaml_content, language)?;
53+
let rules = parse_yaml_rules(&yaml_content, language)?;
5454

5555
// Validate each rule and collect valid ones
5656
let mut valid_rules = Vec::new();
@@ -75,82 +75,83 @@ impl<'a> RuleSynthesizer<'a> {
7575

7676
// Persist valid rules
7777
if !valid_rules.is_empty() {
78-
self.persist_rules(&valid_rules, cwe, language)?;
78+
persist_rules(
79+
&valid_rules,
80+
cwe,
81+
language,
82+
self.config.output_dir.to_string_lossy().as_ref(),
83+
)?;
7984
}
8085

8186
Ok(valid_rules)
8287
}
88+
}
8389

84-
/// Parse YAML rules from LLM response
85-
fn parse_yaml_rules(
86-
&self,
87-
yaml_content: &str,
88-
_language: &str,
89-
) -> Result<Vec<String>, RuleError> {
90-
let mut rules = Vec::new();
91-
let mut current_rule = String::new();
92-
let mut in_rule = false;
93-
94-
for line in yaml_content.lines() {
95-
if line.trim() == "---" {
96-
if !current_rule.is_empty() {
97-
rules.push(current_rule.trim().to_string());
98-
current_rule = String::new();
99-
}
100-
in_rule = true;
101-
continue;
90+
/// Parse YAML rules from LLM response
91+
pub fn parse_yaml_rules(yaml_content: &str, _language: &str) -> Result<Vec<String>, RuleError> {
92+
let mut rules = Vec::new();
93+
let mut current_rule = String::new();
94+
let mut in_rule = false;
95+
96+
for line in yaml_content.lines() {
97+
if line.trim() == "---" {
98+
if !current_rule.is_empty() {
99+
rules.push(current_rule.trim().to_string());
100+
current_rule = String::new();
102101
}
102+
in_rule = true;
103+
continue;
104+
}
103105

104-
if in_rule || line.trim().starts_with("rules:") {
105-
let should_add = !current_rule.is_empty() || !line.trim().is_empty();
106-
if should_add {
107-
current_rule.push_str(line);
108-
current_rule.push('\n');
109-
}
106+
if in_rule || line.trim().starts_with("rules:") {
107+
let should_add = !current_rule.is_empty() || !line.trim().is_empty();
108+
if should_add {
109+
current_rule.push_str(line);
110+
current_rule.push('\n');
110111
}
111112
}
113+
}
112114

113-
// Push last rule
114-
if !current_rule.is_empty() {
115-
rules.push(current_rule.trim().to_string());
116-
}
115+
// Push last rule
116+
if !current_rule.is_empty() {
117+
rules.push(current_rule.trim().to_string());
118+
}
117119

118-
// If no rules found, try to parse as single rule
119-
if rules.is_empty() && !yaml_content.trim().is_empty() {
120-
rules.push(yaml_content.trim().to_string());
121-
}
120+
// If no rules found, try to parse as single rule
121+
if rules.is_empty() && !yaml_content.trim().is_empty() {
122+
rules.push(yaml_content.trim().to_string());
123+
}
122124

123-
Ok(rules)
124-
}
125-
126-
/// Persist valid rules to output directory
127-
fn persist_rules(
128-
&self,
129-
rules: &[SemgrepRule],
130-
cwe: &str,
131-
language: &str,
132-
) -> Result<(), RuleError> {
133-
let output_dir = PathBuf::from(&self.config.output_dir);
134-
std::fs::create_dir_all(&output_dir)
135-
.map_err(|e| RuleError::IoError(format!("Failed to create output directory: {}", e)))?;
136-
137-
for (i, rule) in rules.iter().enumerate() {
138-
let filename = format!("{}_{}_{}.yml", cwe, language, i);
139-
let filepath = output_dir.join(&filename);
140-
141-
std::fs::write(&filepath, &rule.yaml).map_err(|e| {
142-
RuleError::IoError(format!(
143-
"Failed to write rule to {}: {}",
144-
filepath.display(),
145-
e
146-
))
147-
})?;
148-
149-
tracing::info!("Persisted rule: {}", filepath.display());
150-
}
125+
Ok(rules)
126+
}
151127

152-
Ok(())
153-
}
128+
/// Persist valid rules to output directory
129+
pub fn persist_rules(
130+
rules: &[SemgrepRule],
131+
cwe: &str,
132+
language: &str,
133+
output_dir: &str,
134+
) -> Result<(), RuleError> {
135+
let output_dir = PathBuf::from(output_dir);
136+
std::fs::create_dir_all(&output_dir)
137+
.map_err(|e| RuleError::IoError(format!("Failed to create output directory: {}", e)))?;
138+
139+
for (i, rule) in rules.iter().enumerate() {
140+
let filename = format!("{}_{}_{}.yml", cwe, language, i);
141+
let filepath = output_dir.join(&filename);
142+
143+
std::fs::write(&filepath, &rule.yaml).map_err(|e| {
144+
RuleError::IoError(format!(
145+
"Failed to write rule to {}: {}",
146+
filepath.display(),
147+
e
148+
))
149+
})?;
150+
151+
tracing::info!("Persisted rule: {}", filepath.display());
152+
}
153+
154+
Ok(())
154155
}
155156

156157
/// Extract rule ID from YAML content

src/scanner/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pub mod checkpoint;
44
mod core;
55
mod env;
66
mod orchestrator;
7-
mod parallel;
7+
pub(crate) mod parallel;
88
#[cfg(test)]
99
pub(crate) mod phases;
1010
#[cfg(not(test))]
@@ -29,6 +29,12 @@ pub use env::{
2929
get_git_remote_url,
3030
};
3131

32+
// Re-export parallel module types for testing
33+
pub use parallel::{
34+
combine_parallel_results, has_valid_checkpoint_findings, run_indexing_phase,
35+
run_llm_static_phase, run_semgrep_phase, ParallelPhaseConfig, ParallelPhaseResult,
36+
};
37+
3238
// Use the checkpoint module for save/load
3339
use crate::checkpoint::ScanPhase;
3440
use crate::findings::VulnerabilityFinding;

src/tools/diff_analysis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn run_diff(
7373
})
7474
}
7575

76-
fn parse_diff(diff_output: &str) -> (u32, u32, u32) {
76+
pub fn parse_diff(diff_output: &str) -> (u32, u32, u32) {
7777
let lines: Vec<&str> = diff_output.lines().collect();
7878

7979
let mut files_changed = 1u32;

src/variant_search.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl VariantSearcher {
163163
Ok(())
164164
}
165165

166-
fn should_skip_file(path: &Path) -> bool {
166+
pub fn should_skip_file(path: &Path) -> bool {
167167
let ext = path
168168
.extension()
169169
.map(|e| e.to_string_lossy().to_lowercase())
@@ -193,15 +193,22 @@ impl VariantSearcher {
193193
)
194194
}
195195

196-
fn extract_snippet(content: &str, line_num: usize) -> String {
196+
pub fn extract_snippet(content: &str, line_num: usize) -> String {
197197
let lines: Vec<&str> = content.lines().collect();
198-
let start = line_num.saturating_sub(1);
198+
if lines.is_empty() {
199+
return String::new();
200+
}
201+
let start = line_num
202+
.saturating_sub(1)
203+
.min(lines.len().saturating_sub(1));
199204
let end = (line_num + 2).min(lines.len());
200-
205+
if start >= end {
206+
return String::new();
207+
}
201208
lines[start..end].join("\n")
202209
}
203210

204-
fn calculate_similarity(&self, line: &str, pattern: &SearchPattern) -> f32 {
211+
pub fn calculate_similarity(&self, line: &str, pattern: &SearchPattern) -> f32 {
205212
let mut score: f32 = 0.0;
206213

207214
if line.contains(&pattern.vulnerability_type) {

0 commit comments

Comments
 (0)