main.rs
1 // Copyright (c) The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or https://opensource.org/license/mit/. 4 5 mod lint_cpp; 6 mod lint_docs; 7 mod lint_py; 8 mod lint_repo_hygiene; 9 mod lint_text_format; 10 mod util; 11 12 use std::env; 13 use std::fs; 14 use std::process::{Command, ExitCode}; 15 16 use lint_cpp::{ 17 lint_boost_assert, lint_includes_build_config, lint_remove_all, lint_rpc_assert, 18 lint_std_filesystem, 19 }; 20 use lint_docs::{lint_doc_args, lint_doc_release_note_snippets, lint_markdown}; 21 use lint_py::{lint_py_lint, lint_rmtree}; 22 use lint_repo_hygiene::{lint_scripted_diff, lint_subtree}; 23 use lint_text_format::{ 24 lint_commit_msg, lint_tabs_whitespace, lint_trailing_newline, lint_trailing_whitespace, 25 }; 26 use util::{check_output, commit_range, get_git_root, git, LintFn, LintResult}; 27 28 struct Linter { 29 pub description: &'static str, 30 pub name: &'static str, 31 pub lint_fn: LintFn, 32 } 33 34 fn get_linter_list() -> Vec<&'static Linter> { 35 vec![ 36 &Linter { 37 description: "Check that all command line arguments are documented.", 38 name: "doc", 39 lint_fn: lint_doc_args 40 }, 41 &Linter { 42 description: "Check that no symbol from bitcoin-build-config.h is used without the header being included", 43 name: "includes_build_config", 44 lint_fn: lint_includes_build_config 45 }, 46 &Linter { 47 description: "Check that markdown links resolve", 48 name: "markdown", 49 lint_fn: lint_markdown 50 }, 51 &Linter { 52 description: "Lint Python code", 53 name: "py_lint", 54 lint_fn: lint_py_lint, 55 }, 56 &Linter { 57 description: "Check that shutil.rmtree is not used", 58 name: "rmtree", 59 lint_fn: lint_rmtree, 60 }, 61 &Linter { 62 description: "Check that std::filesystem is not used directly", 63 name: "std_filesystem", 64 lint_fn: lint_std_filesystem 65 }, 66 &Linter { 67 description: "Check that remove_all is not used", 68 name: "remove_all", 69 lint_fn: lint_remove_all 70 }, 71 &Linter { 72 description: "Check that fatal assertions are not used in RPC code", 73 name: "rpc_assert", 74 lint_fn: lint_rpc_assert 75 }, 76 &Linter { 77 description: "Check that boost assertions are not used", 78 name: "boost_assert", 79 lint_fn: lint_boost_assert 80 }, 81 &Linter { 82 description: "Check that release note snippets are in the right folder", 83 name: "doc_release_note_snippets", 84 lint_fn: lint_doc_release_note_snippets 85 }, 86 &Linter { 87 description: "Check that subtrees are pure subtrees", 88 name: "subtree", 89 lint_fn: lint_subtree 90 }, 91 &Linter { 92 description: "Check scripted-diffs", 93 name: "scripted_diff", 94 lint_fn: lint_scripted_diff 95 }, 96 &Linter { 97 description: "Check that commit messages have a new line before the body or no body at all.", 98 name: "commit_msg", 99 lint_fn: lint_commit_msg 100 }, 101 &Linter { 102 description: "Check that tabs are not used as whitespace", 103 name: "tabs_whitespace", 104 lint_fn: lint_tabs_whitespace 105 }, 106 &Linter { 107 description: "Check for trailing whitespace", 108 name: "trailing_whitespace", 109 lint_fn: lint_trailing_whitespace 110 }, 111 &Linter { 112 description: "Check for trailing newline", 113 name: "trailing_newline", 114 lint_fn: lint_trailing_newline 115 }, 116 &Linter { 117 description: "Run all linters of the form: test/lint/lint-*.py", 118 name: "all_python_linters", 119 lint_fn: run_all_python_linters 120 }, 121 ] 122 } 123 124 fn print_help_and_exit() { 125 print!( 126 r#" 127 Usage: test_runner [--lint=LINTER_TO_RUN] 128 Runs all linters in the lint test suite, printing any errors 129 they detect. 130 131 If you wish to only run some particular lint tests, pass 132 '--lint=' with the name of the lint test you wish to run. 133 You can set as many '--lint=' values as you wish, e.g.: 134 test_runner --lint=doc --lint=subtree 135 136 The individual linters available to run are: 137 "# 138 ); 139 for linter in get_linter_list() { 140 println!("{}: \"{}\"", linter.name, linter.description) 141 } 142 143 std::process::exit(1); 144 } 145 146 fn parse_lint_args(args: &[String]) -> Vec<&'static Linter> { 147 let linter_list = get_linter_list(); 148 let mut lint_values = Vec::new(); 149 150 for arg in args { 151 #[allow(clippy::if_same_then_else)] 152 if arg.starts_with("--lint=") { 153 let lint_arg_value = arg 154 .trim_start_matches("--lint=") 155 .trim_matches('"') 156 .trim_matches('\''); 157 158 let try_find_linter = linter_list 159 .iter() 160 .find(|linter| linter.name == lint_arg_value); 161 match try_find_linter { 162 Some(linter) => { 163 lint_values.push(*linter); 164 } 165 None => { 166 println!("No linter {lint_arg_value} found!"); 167 print_help_and_exit(); 168 } 169 } 170 } else if arg.eq("--help") || arg.eq("-h") { 171 print_help_and_exit(); 172 } else { 173 print_help_and_exit(); 174 } 175 } 176 177 lint_values 178 } 179 180 fn run_all_python_linters() -> LintResult { 181 let mut good = true; 182 let lint_dir = get_git_root().join("test/lint"); 183 for entry in fs::read_dir(lint_dir).unwrap() { 184 let entry = entry.unwrap(); 185 let entry_fn = entry.file_name().into_string().unwrap(); 186 if entry_fn.starts_with("lint-") 187 && entry_fn.ends_with(".py") 188 && !Command::new("python3") 189 .arg(entry.path()) 190 .status() 191 .expect("command error") 192 .success() 193 { 194 good = false; 195 println!("^---- ⚠️ Failure generated from {entry_fn}"); 196 } 197 } 198 if good { 199 Ok(()) 200 } else { 201 Err("".to_string()) 202 } 203 } 204 205 fn main() -> ExitCode { 206 let linters_to_run: Vec<&Linter> = if env::args().count() > 1 { 207 let args: Vec<String> = env::args().skip(1).collect(); 208 parse_lint_args(&args) 209 } else { 210 // If no arguments are passed, run all linters. 211 get_linter_list() 212 }; 213 214 let git_root = get_git_root(); 215 let commit_range = commit_range(); 216 let commit_log = check_output(git().args(["log", "--no-merges", "--oneline", &commit_range])) 217 .expect("check_output failed"); 218 println!("Checking commit range ({commit_range}):\n{commit_log}\n"); 219 220 let mut test_failed = false; 221 for linter in linters_to_run { 222 // chdir to root before each lint test 223 env::set_current_dir(&git_root).unwrap(); 224 if let Err(err) = (linter.lint_fn)() { 225 println!( 226 "^^^\n{err}\n^---- ⚠️ Failure generated from lint check '{}' ({})!\n\n", 227 linter.name, linter.description, 228 ); 229 test_failed = true; 230 } 231 } 232 if test_failed { 233 ExitCode::FAILURE 234 } else { 235 ExitCode::SUCCESS 236 } 237 }