diff options
Diffstat (limited to 'test/lint/test_runner/src/main.rs')
-rw-r--r-- | test/lint/test_runner/src/main.rs | 186 |
1 files changed, 170 insertions, 16 deletions
diff --git a/test/lint/test_runner/src/main.rs b/test/lint/test_runner/src/main.rs index d5dd98effe..9c35898c1f 100644 --- a/test/lint/test_runner/src/main.rs +++ b/test/lint/test_runner/src/main.rs @@ -4,14 +4,121 @@ use std::env; use std::fs; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use std::process::Command; -use std::process::ExitCode; +use std::process::{Command, ExitCode, Stdio}; type LintError = String; type LintResult = Result<(), LintError>; type LintFn = fn() -> LintResult; +struct Linter { + pub description: &'static str, + pub name: &'static str, + pub lint_fn: LintFn, +} + +fn get_linter_list() -> Vec<&'static Linter> { + vec![ + &Linter { + description: "Check that all command line arguments are documented.", + name: "doc", + lint_fn: lint_doc + }, + &Linter { + description: "Check that no symbol from bitcoin-config.h is used without the header being included", + name: "includes_build_config", + lint_fn: lint_includes_build_config + }, + &Linter { + description: "Check that markdown links resolve", + name: "markdown", + lint_fn: lint_markdown + }, + &Linter { + description: "Check that std::filesystem is not used directly", + name: "std_filesystem", + lint_fn: lint_std_filesystem + }, + &Linter { + description: "Check that subtrees are pure subtrees", + name: "subtree", + lint_fn: lint_subtree + }, + &Linter { + description: "Check that tabs are not used as whitespace", + name: "tabs_whitespace", + lint_fn: lint_tabs_whitespace + }, + &Linter { + description: "Check for trailing whitespace", + name: "trailing_whitespace", + lint_fn: lint_trailing_whitespace + }, + &Linter { + description: "Run all linters of the form: test/lint/lint-*.py", + name: "all_python_linters", + lint_fn: run_all_python_linters + }, + ] +} + +fn print_help_and_exit() { + print!( + r#" +Usage: test_runner [--lint=LINTER_TO_RUN] +Runs all linters in the lint test suite, printing any errors +they detect. + +If you wish to only run some particular lint tests, pass +'--lint=' with the name of the lint test you wish to run. +You can set as many '--lint=' values as you wish, e.g.: +test_runner --lint=doc --lint=subtree + +The individual linters available to run are: +"# + ); + for linter in get_linter_list() { + println!("{}: \"{}\"", linter.name, linter.description) + } + + std::process::exit(1); +} + +fn parse_lint_args(args: &[String]) -> Vec<&'static Linter> { + let linter_list = get_linter_list(); + let mut lint_values = Vec::new(); + + for arg in args { + #[allow(clippy::if_same_then_else)] + if arg.starts_with("--lint=") { + let lint_arg_value = arg + .trim_start_matches("--lint=") + .trim_matches('"') + .trim_matches('\''); + + let try_find_linter = linter_list + .iter() + .find(|linter| linter.name == lint_arg_value); + match try_find_linter { + Some(linter) => { + lint_values.push(*linter); + } + None => { + println!("No linter {lint_arg_value} found!"); + print_help_and_exit(); + } + } + } else if arg.eq("--help") || arg.eq("-h") { + print_help_and_exit(); + } else { + print_help_and_exit(); + } + } + + lint_values +} + /// Return the git command fn git() -> Command { let mut git = Command::new("git"); @@ -292,7 +399,52 @@ fn lint_doc() -> LintResult { } } -fn lint_all() -> LintResult { +fn lint_markdown() -> LintResult { + let bin_name = "mlc"; + let mut md_ignore_paths = get_subtrees(); + md_ignore_paths.push("./doc/README_doxygen.md"); + let md_ignore_path_str = md_ignore_paths.join(","); + + let mut cmd = Command::new(bin_name); + cmd.args([ + "--offline", + "--ignore-path", + md_ignore_path_str.as_str(), + "--root-dir", + ".", + ]) + .stdout(Stdio::null()); // Suppress overly-verbose output + + match cmd.output() { + Ok(output) if output.status.success() => Ok(()), + Ok(output) => { + let stderr = String::from_utf8_lossy(&output.stderr); + let filtered_stderr: String = stderr // Filter out this annoying trailing line + .lines() + .filter(|&line| line != "The following links could not be resolved:") + .collect::<Vec<&str>>() + .join("\n"); + Err(format!( + r#" +One or more markdown links are broken. + +Relative links are preferred (but not required) as jumping to file works natively within Emacs. + +Markdown link errors found: +{} + "#, + filtered_stderr + )) + } + Err(e) if e.kind() == ErrorKind::NotFound => { + println!("`mlc` was not found in $PATH, skipping markdown lint check."); + Ok(()) + } + Err(e) => Err(format!("Error running mlc: {}", e)), // Misc errors + } +} + +fn run_all_python_linters() -> LintResult { let mut good = true; let lint_dir = get_git_root().join("test/lint"); for entry in fs::read_dir(lint_dir).unwrap() { @@ -307,7 +459,7 @@ fn lint_all() -> LintResult { .success() { good = false; - println!("^---- failure generated from {}", entry_fn); + println!("^---- ⚠️ Failure generated from {}", entry_fn); } } if good { @@ -318,24 +470,26 @@ fn lint_all() -> LintResult { } fn main() -> ExitCode { - let test_list: Vec<(&str, LintFn)> = vec![ - ("subtree check", lint_subtree), - ("std::filesystem check", lint_std_filesystem), - ("trailing whitespace check", lint_trailing_whitespace), - ("no-tabs check", lint_tabs_whitespace), - ("build config includes check", lint_includes_build_config), - ("-help=1 documentation check", lint_doc), - ("lint-*.py scripts", lint_all), - ]; + let linters_to_run: Vec<&Linter> = if env::args().count() > 1 { + let args: Vec<String> = env::args().skip(1).collect(); + parse_lint_args(&args) + } else { + // If no arguments are passed, run all linters. + get_linter_list() + }; let git_root = get_git_root(); let mut test_failed = false; - for (lint_name, lint_fn) in test_list { + for linter in linters_to_run { // chdir to root before each lint test env::set_current_dir(&git_root).unwrap(); - if let Err(err) = lint_fn() { - println!("{err}\n^---- ⚠️ Failure generated from {lint_name}!"); + if let Err(err) = (linter.lint_fn)() { + println!( + "{err}\n^---- ⚠️ Failure generated from lint check '{}'!", + linter.name + ); + println!("{}", linter.description); test_failed = true; } } |