/ test / lint / test_runner / src / lint_py.rs
lint_py.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::io::ErrorKind;
  6  use std::process::Command;
  7  
  8  use crate::util::{check_output, get_pathspecs_default_excludes, git, LintResult};
  9  
 10  pub fn lint_py_lint() -> LintResult {
 11      let bin_name = "ruff";
 12      let checks = format!(
 13          "--select={}",
 14          [
 15              "B006", // mutable-argument-default
 16              "B008", // function-call-in-default-argument
 17              "E101", // indentation contains mixed spaces and tabs
 18              "E401", // multiple imports on one line
 19              "E402", // module level import not at top of file
 20              "E701", // multiple statements on one line (colon)
 21              "E702", // multiple statements on one line (semicolon)
 22              "E703", // statement ends with a semicolon
 23              "E711", // comparison to None should be 'if cond is None:'
 24              "E713", // test for membership should be "not in"
 25              "E714", // test for object identity should be "is not"
 26              "E721", // do not compare types, use "isinstance()"
 27              "E722", // do not use bare 'except'
 28              "E742", // do not define classes named "l", "O", or "I"
 29              "E743", // do not define functions named "l", "O", or "I"
 30              "F401", // module imported but unused
 31              "F402", // import module from line N shadowed by loop variable
 32              "F403", // 'from foo_module import *' used; unable to detect undefined names
 33              "F404", // future import(s) name after other statements
 34              "F405", // foo_function may be undefined, or defined from star imports: bar_module
 35              "F406", // "from module import *" only allowed at module level
 36              "F407", // an undefined __future__ feature name was imported
 37              "F541", // f-string without any placeholders
 38              "F601", // dictionary key name repeated with different values
 39              "F602", // dictionary key variable name repeated with different values
 40              "F621", // too many expressions in an assignment with star-unpacking
 41              "F631", // assertion test is a tuple, which are always True
 42              "F632", // use ==/!= to compare str, bytes, and int literals
 43              "F811", // redefinition of unused name from line N
 44              "F821", // undefined name 'Foo'
 45              "F822", // undefined name name in __all__
 46              "F823", // local variable name … referenced before assignment
 47              "F841", // local variable 'foo' is assigned to but never used
 48              "PLE",  // Pylint errors
 49              "W191", // indentation contains tabs
 50              "W291", // trailing whitespace
 51              "W292", // no newline at end of file
 52              "W293", // blank line contains whitespace
 53              "W605", // invalid escape sequence "x"
 54          ]
 55          .join(",")
 56      );
 57      let files = check_output(
 58          git()
 59              .args(["ls-files", "--", "*.py"])
 60              .args(get_pathspecs_default_excludes()),
 61      )?;
 62  
 63      let mut cmd = Command::new(bin_name);
 64      cmd.args(["check", &checks]).args(files.lines());
 65  
 66      match cmd.status() {
 67          Ok(status) if status.success() => Ok(()),
 68          Ok(_) => Err(format!("`{bin_name}` found errors!")),
 69          Err(e) if e.kind() == ErrorKind::NotFound => {
 70              println!("`{bin_name}` was not found in $PATH, skipping those checks.");
 71              Ok(())
 72          }
 73          Err(e) => Err(format!("Error running `{bin_name}`: {e}")),
 74      }
 75  }
 76  
 77  pub fn lint_rmtree() -> LintResult {
 78      let found = git()
 79          .args([
 80              "grep",
 81              "--line-number",
 82              "rmtree",
 83              "--",
 84              "test/functional/",
 85              ":(exclude)test/functional/test_framework/test_framework.py",
 86          ])
 87          .status()
 88          .expect("command error")
 89          .success();
 90      if found {
 91          Err(r#"
 92  Use of shutil.rmtree() is dangerous and should be avoided. If it
 93  is really required for the test, use self.cleanup_folder(_).
 94              "#
 95          .trim()
 96          .to_string())
 97      } else {
 98          Ok(())
 99      }
100  }