/ test / lint / test_runner / src / util.rs
util.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  use std::env;
 6  use std::path::PathBuf;
 7  use std::process::Command;
 8  
 9  /// A possible error returned by any of the linters.
10  ///
11  /// The error string should explain the failure type and list all violations.
12  pub type LintError = String;
13  pub type LintResult = Result<(), LintError>;
14  pub type LintFn = fn() -> LintResult;
15  
16  /// Return the git command
17  ///
18  /// Lint functions should use this command, so that only files tracked by git are considered and
19  /// temporary and untracked files are ignored. For example, instead of 'grep', 'git grep' should be
20  /// used.
21  pub fn git() -> Command {
22      let mut git = Command::new("git");
23      git.arg("--no-pager");
24      git
25  }
26  
27  /// Return stdout on success and a LintError on failure, when invalid UTF8 was detected or the
28  /// command did not succeed.
29  pub fn check_output(cmd: &mut Command) -> Result<String, LintError> {
30      let out = cmd.output().expect("command error");
31      if !out.status.success() {
32          return Err(String::from_utf8_lossy(&out.stderr).to_string());
33      }
34      Ok(String::from_utf8(out.stdout)
35          .map_err(|e| {
36              format!("All path names, source code, messages, and output must be valid UTF8!\n{e}")
37          })?
38          .trim()
39          .to_string())
40  }
41  
42  /// Return the git root as utf8, or panic
43  pub fn get_git_root() -> PathBuf {
44      PathBuf::from(check_output(git().args(["rev-parse", "--show-toplevel"])).unwrap())
45  }
46  
47  /// Return the commit range, or panic
48  pub fn commit_range() -> String {
49      // Use the env var, if set. E.g. COMMIT_RANGE='HEAD~n..HEAD' for the last 'n' commits.
50      env::var("COMMIT_RANGE").unwrap_or_else(|_| {
51          // Otherwise, assume that a merge commit exists. This merge commit is assumed
52          // to be the base, after which linting will be done. If the merge commit is
53          // HEAD, the range will be empty.
54          format!(
55              "{}..HEAD",
56              check_output(git().args(["rev-list", "--max-count=1", "--merges", "HEAD"]))
57                  .expect("check_output failed")
58          )
59      })
60  }
61  
62  /// Return all subtree paths
63  pub fn get_subtrees() -> Vec<&'static str> {
64      // Keep in sync with [test/lint/README.md#git-subtree-checksh]
65      vec![
66          "src/crc32c",
67          "src/crypto/ctaes",
68          "src/ipc/libmultiprocess",
69          "src/leveldb",
70          "src/minisketch",
71          "src/secp256k1",
72      ]
73  }
74  
75  /// Return the pathspecs to exclude by default
76  pub fn get_pathspecs_default_excludes() -> Vec<String> {
77      get_subtrees()
78          .iter()
79          .chain(&[
80              "doc/release-notes/release-notes-*", // archived notes
81          ])
82          .map(|s| format!(":(exclude){s}"))
83          .collect()
84  }