main.rs raw
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 }
238