Skip to content

Commit 700937b

Browse files
committed
WIP add tests to report generator
1 parent bd5bc77 commit 700937b

File tree

1 file changed

+357
-0
lines changed

1 file changed

+357
-0
lines changed

.github/actions/clippy-annotation-reporter/src/report_generator.rs

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,360 @@ fn get_all_keys<K: Clone + Ord + std::hash::Hash, V>(
234234

235235
keys_vec
236236
}
237+
238+
#[cfg(test)]
239+
mod tests {
240+
use super::*;
241+
use crate::analyzer::ClippyAnnotation;
242+
use std::collections::{HashMap, HashSet};
243+
use std::rc::Rc;
244+
245+
// Helper function to create a test AnalysisResult
246+
fn create_analysis_result() -> crate::analyzer::AnalysisResult {
247+
let mut base_counts = HashMap::new();
248+
let rule1 = Rc::new("clippy::unwrap_used".to_owned());
249+
let rule2 = Rc::new("clippy::match_bool".to_owned());
250+
let rule3 = Rc::new("clippy::unused_imports".to_owned());
251+
252+
base_counts.insert(rule1.clone(), 5);
253+
base_counts.insert(rule2.clone(), 3);
254+
base_counts.insert(rule3.clone(), 10);
255+
256+
let mut head_counts = HashMap::new();
257+
head_counts.insert(rule1.clone(), 3);
258+
head_counts.insert(rule2.clone(), 4);
259+
head_counts.insert(rule3.clone(), 5);
260+
261+
let mut base_crate_counts = HashMap::new();
262+
let crate1 = Rc::new("crate1".to_owned());
263+
let crate2 = Rc::new("crate2".to_owned());
264+
265+
base_crate_counts.insert(crate1.clone(), 8);
266+
base_crate_counts.insert(crate2.clone(), 10);
267+
268+
let mut head_crate_counts = HashMap::new();
269+
head_crate_counts.insert(crate1.clone(), 5);
270+
head_crate_counts.insert(crate2.clone(), 12);
271+
272+
let mut changed_files = HashSet::new();
273+
changed_files.insert("src/file1.rs".to_owned());
274+
changed_files.insert("src/file2.rs".to_owned());
275+
276+
let file1 = Rc::new("src/file1.rs".to_owned());
277+
let file2 = Rc::new("src/file2.rs".to_owned());
278+
279+
let base_annotations = vec![
280+
ClippyAnnotation {
281+
file: file1.clone(),
282+
rule: rule1.clone(),
283+
},
284+
ClippyAnnotation {
285+
file: file1.clone(),
286+
rule: rule1.clone(),
287+
},
288+
ClippyAnnotation {
289+
file: file1.clone(),
290+
rule: rule2.clone(),
291+
},
292+
ClippyAnnotation {
293+
file: file2.clone(),
294+
rule: rule1.clone(),
295+
},
296+
ClippyAnnotation {
297+
file: file2.clone(),
298+
rule: rule3.clone(),
299+
},
300+
];
301+
302+
let head_annotations = vec![
303+
ClippyAnnotation {
304+
file: file1.clone(),
305+
rule: rule1.clone(),
306+
},
307+
ClippyAnnotation {
308+
file: file1.clone(),
309+
rule: rule2.clone(),
310+
},
311+
ClippyAnnotation {
312+
file: file1.clone(),
313+
rule: rule2.clone(),
314+
},
315+
ClippyAnnotation {
316+
file: file2.clone(),
317+
rule: rule3.clone(),
318+
},
319+
];
320+
321+
crate::analyzer::AnalysisResult {
322+
base_annotations,
323+
head_annotations,
324+
base_counts,
325+
head_counts,
326+
changed_files,
327+
base_crate_counts,
328+
head_crate_counts,
329+
}
330+
}
331+
332+
#[test]
333+
fn test_generate_report_basic() {
334+
let analysis = create_analysis_result();
335+
let rules = vec![
336+
"clippy::unwrap_used".to_owned(),
337+
"clippy::match_bool".to_owned(),
338+
"clippy::unused_imports".to_owned(),
339+
];
340+
341+
let report = generate_report(
342+
&analysis,
343+
&rules,
344+
"test-owner/test-repo",
345+
"main",
346+
"feature-branch",
347+
);
348+
349+
// Verify the report contains expected sections
350+
assert!(report.contains("## Clippy Allow Annotation Report"));
351+
assert!(report.contains("### Summary by Rule"));
352+
assert!(report.contains("### Annotation Counts by File"));
353+
assert!(report.contains("### Annotation Stats by Crate"));
354+
assert!(report.contains("### About This Report"));
355+
356+
// Verify the report contains repository and branch information
357+
assert!(report.contains("test-owner/test-repo"));
358+
assert!(report.contains("main"));
359+
assert!(report.contains("feature-branch"));
360+
}
361+
362+
#[test]
363+
fn test_generate_report_rule_summary() {
364+
let analysis = create_analysis_result();
365+
let rules = vec![
366+
"clippy::unwrap_used".to_owned(),
367+
"clippy::match_bool".to_owned(),
368+
"clippy::unused_imports".to_owned(),
369+
];
370+
371+
let report = generate_report(
372+
&analysis,
373+
&rules,
374+
"test-owner/test-repo",
375+
"main",
376+
"feature-branch",
377+
);
378+
379+
// Verify rule summary contains all rules
380+
assert!(report.contains("clippy::unwrap_used"));
381+
assert!(report.contains("clippy::match_bool"));
382+
assert!(report.contains("clippy::unused_imports"));
383+
384+
// Verify counts and changes
385+
assert!(report.contains("5")); // Base count for unwrap_used
386+
assert!(report.contains("3")); // Head count for unwrap_used
387+
assert!(report.contains("-2")); // Change for unwrap_used
388+
389+
assert!(report.contains("3")); // Base count for match_bool
390+
assert!(report.contains("4")); // Head count for match_bool
391+
assert!(report.contains("+1")); // Change for match_bool
392+
393+
assert!(report.contains("10")); // Base count for unused_imports
394+
assert!(report.contains("5")); // Head count for unused_imports
395+
assert!(report.contains("-5")); // Change for unused_imports
396+
}
397+
398+
#[test]
399+
fn test_generate_report_file_section() {
400+
let analysis = create_analysis_result();
401+
let rules = vec![
402+
"clippy::unwrap_used".to_owned(),
403+
"clippy::match_bool".to_owned(),
404+
"clippy::unused_imports".to_owned(),
405+
];
406+
407+
let report = generate_report(
408+
&analysis,
409+
&rules,
410+
"test-owner/test-repo",
411+
"main",
412+
"feature-branch",
413+
);
414+
415+
// Verify file section contains the changed files
416+
assert!(report.contains("src/file1.rs"));
417+
assert!(report.contains("src/file2.rs"));
418+
419+
// Verify file counts for file1.rs
420+
// In the base branch, file1.rs has 3 annotations (2 unwrap_used, 1 match_bool)
421+
// In the head branch, file1.rs has 3 annotations (1 unwrap_used, 2 match_bool)
422+
let file1_pattern = r"`src/file1\.rs`\s*\|\s*3\s*\|\s*3\s*\|\s*No change";
423+
assert!(
424+
report.contains("| `src/file1.rs` | 3 | 3 |")
425+
|| regex::Regex::new(file1_pattern).unwrap().is_match(&report),
426+
"File1 count information not found in report"
427+
);
428+
429+
// Verify file counts for file2.rs
430+
// In the base branch, file2.rs has 2 annotations (1 unwrap_used, 1 unused_imports)
431+
// In the head branch, file2.rs has 1 annotation (1 unused_imports)
432+
let file2_pattern = r"`src/file2\.rs`\s*\|\s*2\s*\|\s*1\s*\|\s*.*-1";
433+
assert!(
434+
report.contains("| `src/file2.rs` | 2 | 1 |")
435+
|| regex::Regex::new(file2_pattern).unwrap().is_match(&report),
436+
"File2 count information not found in report"
437+
);
438+
439+
// Make sure the change column has the correct indicators
440+
assert!(
441+
report.contains("No change") || report.contains("(0%)"),
442+
"No change indicator missing for file1"
443+
);
444+
assert!(
445+
report.contains("✅ -1"),
446+
"Decrease indicator missing for file2"
447+
);
448+
}
449+
450+
#[test]
451+
fn test_generate_report_crate_section() {
452+
let analysis = create_analysis_result();
453+
let rules = vec![
454+
"clippy::unwrap_used".to_owned(),
455+
"clippy::match_bool".to_owned(),
456+
"clippy::unused_imports".to_owned(),
457+
];
458+
459+
let report = generate_report(
460+
&analysis,
461+
&rules,
462+
"test-owner/test-repo",
463+
"main",
464+
"feature-branch",
465+
);
466+
467+
// Verify crate section contains the crates
468+
assert!(report.contains("`crate1`"));
469+
assert!(report.contains("`crate2`"));
470+
471+
// Verify crate counts
472+
// Base count for crate1: 8, Head count: 5
473+
assert!(report.contains("8"));
474+
assert!(report.contains("5"));
475+
assert!(report.contains("-3")); // Change
476+
477+
// Base count for crate2: 10, Head count: 12
478+
assert!(report.contains("10"));
479+
assert!(report.contains("12"));
480+
assert!(report.contains("+2")); // Change
481+
}
482+
483+
#[test]
484+
fn test_generate_report_empty_changed_files() {
485+
let mut analysis = create_analysis_result();
486+
analysis.changed_files.clear();
487+
488+
let rules = vec![
489+
"clippy::unwrap_used".to_owned(),
490+
"clippy::match_bool".to_owned(),
491+
"clippy::unused_imports".to_owned(),
492+
];
493+
494+
let report = generate_report(
495+
&analysis,
496+
&rules,
497+
"test-owner/test-repo",
498+
"main",
499+
"feature-branch",
500+
);
501+
502+
// Verify that the file-level section is not included when there are no changed files
503+
assert!(!report.contains("### Annotation Counts by File"));
504+
505+
// But other sections should still be present
506+
assert!(report.contains("### Summary by Rule"));
507+
assert!(report.contains("### Annotation Stats by Crate"));
508+
}
509+
510+
#[test]
511+
fn test_generate_report_formatting() {
512+
let analysis = create_analysis_result();
513+
let rules = vec![
514+
"clippy::unwrap_used".to_owned(),
515+
"clippy::match_bool".to_owned(),
516+
"clippy::unused_imports".to_owned(),
517+
];
518+
519+
let report = generate_report(
520+
&analysis,
521+
&rules,
522+
"test-owner/test-repo",
523+
"main",
524+
"feature-branch",
525+
);
526+
527+
// Verify positive changes are formatted with ⚠️
528+
assert!(report.contains("⚠️ +1"));
529+
530+
// Verify negative changes are formatted with ✅
531+
assert!(report.contains("✅ -2"));
532+
533+
// Verify total row exists
534+
assert!(report.contains("**Total**"));
535+
}
536+
537+
#[test]
538+
fn test_generate_report_with_origin_prefix() {
539+
let analysis = create_analysis_result();
540+
let rules = vec![
541+
"clippy::unwrap_used".to_owned(),
542+
"clippy::match_bool".to_owned(),
543+
"clippy::unused_imports".to_owned(),
544+
];
545+
546+
// Test with origin/ prefix in base branch
547+
let report = generate_report(
548+
&analysis,
549+
&rules,
550+
"test-owner/test-repo",
551+
"origin/main",
552+
"feature-branch",
553+
);
554+
555+
// Verify the origin/ prefix is removed in the link URL
556+
assert!(report.contains("https://github.com/test-owner/test-repo/tree/main"));
557+
assert!(report.contains("origin/main"));
558+
}
559+
560+
#[test]
561+
fn test_generate_report_new_annotations() {
562+
// Create an analysis where annotations are added in the head branch
563+
let mut analysis = create_analysis_result();
564+
565+
// Add a new rule that only appears in the head counts
566+
let new_rule = Rc::new("clippy::new_rule".to_owned());
567+
analysis.head_counts.insert(new_rule.clone(), 2);
568+
569+
let rules = vec![
570+
"clippy::unwrap_used".to_owned(),
571+
"clippy::match_bool".to_owned(),
572+
"clippy::unused_imports".to_owned(),
573+
"clippy::new_rule".to_owned(),
574+
];
575+
576+
let report = generate_report(
577+
&analysis,
578+
&rules,
579+
"test-owner/test-repo",
580+
"main",
581+
"feature-branch",
582+
);
583+
584+
// Verify that new rule appears with appropriate formatting
585+
assert!(report.contains("clippy::new_rule"));
586+
assert!(report.contains("0")); // Base count should be 0
587+
assert!(report.contains("2")); // Head count should be 2
588+
assert!(report.contains("⚠️ +2")); // Change should be +2
589+
590+
// Also verify N/A for the percentage since base count is 0
591+
assert!(report.contains("N/A"));
592+
}
593+
}

0 commit comments

Comments
 (0)