testing_conventions/
lib.rs1pub mod config;
2pub mod location;
3
4use std::path::{Path, PathBuf};
5
6use clap::{Parser, Subcommand};
7
8#[derive(Parser, Debug)]
9#[command(
10 name = "testing-conventions",
11 version,
12 about = "Enforce testing conventions in libraries (Python, TypeScript, and Rust).",
13 long_about = None,
14)]
15pub struct Cli {
16 #[command(subcommand)]
17 command: Option<Command>,
18}
19
20#[derive(Subcommand, Debug)]
21enum Command {
22 Check,
24 Unit {
26 #[command(subcommand)]
27 rule: UnitRule,
28 },
29}
30
31#[derive(Subcommand, Debug)]
33enum UnitRule {
34 Location {
36 path: PathBuf,
38 #[arg(long, value_enum)]
40 language: location::Language,
41 },
42}
43
44pub fn run<I, T>(args: I) -> anyhow::Result<i32>
45where
46 I: IntoIterator<Item = T>,
47 T: Into<std::ffi::OsString> + Clone,
48{
49 let cli = Cli::try_parse_from(args)?;
50 match cli.command {
51 Some(Command::Check) | None => Ok(0),
55 Some(Command::Unit {
56 rule: UnitRule::Location { path, language },
57 }) => run_unit_location(&path, language),
58 }
59}
60
61fn run_unit_location(root: &Path, language: location::Language) -> anyhow::Result<i32> {
66 let orphans = location::missing_unit_tests(root, language)?;
67 if orphans.is_empty() {
68 return Ok(0);
69 }
70 for orphan in &orphans {
71 eprintln!("missing colocated unit test: {}", orphan.display());
72 }
73 eprintln!(
74 "error: {} source file(s) missing a colocated unit test",
75 orphans.len()
76 );
77 Ok(1)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn no_args_returns_ok_zero() {
86 assert_eq!(run(["testing-conventions"]).unwrap(), 0);
87 }
88
89 #[test]
90 fn check_returns_ok_zero() {
91 assert_eq!(run(["testing-conventions", "check"]).unwrap(), 0);
92 }
93
94 #[test]
95 fn unknown_flag_errors() {
96 assert!(run(["testing-conventions", "--bogus"]).is_err());
97 }
98
99 #[test]
100 fn help_flag_returns_clap_display_help() {
101 let err = run(["testing-conventions", "--help"]).expect_err("--help should bubble");
102 let clap_err = err
103 .downcast_ref::<clap::Error>()
104 .expect("error should be a clap::Error");
105 assert_eq!(clap_err.kind(), clap::error::ErrorKind::DisplayHelp);
106 }
107
108 #[test]
109 fn version_flag_returns_clap_display_version() {
110 let err = run(["testing-conventions", "--version"]).expect_err("--version should bubble");
111 let clap_err = err
112 .downcast_ref::<clap::Error>()
113 .expect("error should be a clap::Error");
114 assert_eq!(clap_err.kind(), clap::error::ErrorKind::DisplayVersion);
115 }
116}