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