From 48c281b421b13ed4e642f1ac5f310af603e1ea2d Mon Sep 17 00:00:00 2001 From: johndoknjas Date: Mon, 6 May 2024 16:39:51 -0700 Subject: [PATCH] Replace some testing and linting code with the `lintception` library. --- .github/workflows/run-tests.yml | 2 +- README.md | 8 ++--- ideas.txt | 10 +++--- my-linter.py | 59 --------------------------------- tests.py | 53 ++--------------------------- 5 files changed, 13 insertions(+), 119 deletions(-) delete mode 100644 my-linter.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4cbe8c2..d231ba6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -32,7 +32,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest mypy vulture vermin requests rich deepdiff mplcursors + pip install flake8 pytest lintception requests rich deepdiff mplcursors - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names diff --git a/README.md b/README.md index 45db674..6422b37 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ Open the root directory of the project in the terminal, and then: - `python main.py *args*` allows you to test any local changes you've made to the project. - `vulture .` will find unused code. - `mypy .` will typecheck. + - `vermin .` deduces the oldest version of python that works to run the project. The expected output is 3.8, corresponding with `requires-python = ">= 3.8"` in `pyproject.toml`. - `pylint *.py` will review the code for style. - `pydeps hypickle` will output a dependency graph of the project's modules. - - `python my-linter.py` is a basic script I wrote that mainly attempts to find functions which are never/rarely used. - - `pytest tests.py` runs a few basic automated tests. Note that this requires installing `vulture`, - `mypy`, and `vermin`. To run manual tests (i.e., the output to the screen needs to be judged by the tester), run `python tests.py`. - - `vermin hypickle` deduces the oldest version of python that works to run the project. The expected output is 3.8, corresponding with `requires-python = ">= 3.8"` in `pyproject.toml`. + - `lintception` is a script I wrote that calls mypy, vulture, and vermin, and also does some other linting checks (e.g., functions which are never/rarely used). Requires installing with `pip install lintception`. + - `pytest tests.py` runs a few basic automated tests. Note that this requires installing the `lintception` + library. To run manual tests (i.e., the output to the screen needs to be judged by the tester), run `python tests.py`. ### Acknowledgements: diff --git a/ideas.txt b/ideas.txt index 0159c6d..5cad0e1 100644 --- a/ideas.txt +++ b/ideas.txt @@ -16,6 +16,11 @@ # Run pylint as a test in `tests.py`, where you assert certain error codes aren't raised. For flake8, you can update the `run-tests` file to include more error codes there as well. + # Update the vermin test in lintception to allow the user to test for an exact entire output message. + Then, add this expected message in a .lintception file for this project. + - Also once lintception is updated with allowing the user to specify certain files that should + be empty, specify the __init__.py in the hypickle folder. + # https://github.com/actions/setup-python/issues/808 When python 3.8 and 3.9 reliably work on macos for github runners, remove the exclusion of them in `run-tests.py`. @@ -52,11 +57,6 @@ - Can also output whether any friends (on a normal program run) appear on any leaderboards, and if so which. - # For the `my-linter.py` script, consider adding a way to only count a function call if it's - non-recursive. - # E.g., could track the lines of all the functions in the project, and then if the latest function - a call is after is the same function as the one being called, it's likely recursive (or a global call). - # Consider making a report class instead of report dicts. The class could have a function that makes and returns a dict. diff --git a/my-linter.py b/my-linter.py deleted file mode 100644 index f9f3246..0000000 --- a/my-linter.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations -import glob -from dataclasses import dataclass - -@dataclass -class Func: - name: str - line_index: int - -def find_funcs(lines: list[str]) -> list[Func]: - """`lines` are all the lines of code. The function will go through it and find all function definitions, - putting each function name and line index into the list that's returned.""" - funcs: list[Func] = [] - for i, code_line in enumerate(lines): - words = code_line.split() - if not words or words[0] != 'def': - continue - assert '(' in words[1] - funcs.append(Func(words[1].split('(')[0], i)) - return funcs - -def find_func_references(lines: list[str], func: Func) -> list[int]: - """Note that this doesn't include the function's definition.""" - return [i for (i, line) in enumerate(lines) if func.name in line and i != func.line_index] - -def output_funcs_with_no_arrow(lines: list[str], funcs: list[Func]) -> None: - print("Functions without a '->':\n") - for func in funcs: - if all('->' not in line for line in lines[func.line_index:func.line_index+3]): - print(f"{func} possibly does not have a '->'") - -def main() -> None: - lines: list[str] = [] - for filename in glob.iglob('**/*.py', recursive=True): - if filename == "tests.py": - continue - with open(filename) as file: - lines.extend(file.read().splitlines()) - funcs: list[Func] = find_funcs(lines) - output_funcs_with_no_arrow(lines, funcs) - funcs_used_once: list[tuple[Func, int]] = [] - print("\n\nUnused functions:\n") - for func in funcs: - references = find_func_references(lines, func) - if len(references) == 0: - print(f"******{func} is unused******") - elif len(references) == 1: - funcs_used_once.append((func, references[0])) - funcs_used_once.sort(key=lambda f: - ((defined_vs_used := f[0].line_index-f[1]) < 0, -abs(defined_vs_used)), - reverse=True) - print("\n\nFunctions used only once:\n") - for f in funcs_used_once: - print(f"{f[0]} is only referenced at line index {f[1]}") - -if __name__ == '__main__': - main() -else: - raise ImportError("This module shouldn't be imported.") \ No newline at end of file diff --git a/tests.py b/tests.py index 2a33174..aea7cf1 100644 --- a/tests.py +++ b/tests.py @@ -1,10 +1,6 @@ from __future__ import annotations -import subprocess -from subprocess import PIPE -import glob +from lintception import linters # type: ignore import pytest -import vulture # type: ignore -import mypy.api from hypickle.MyClasses import Specs from hypickle import leveling, Colours @@ -69,51 +65,8 @@ def test_leveling_errors(self): with pytest.raises(AssertionError): leveling.getTotalExpToLevelFloor(1.9999) - def test_vulture(self): - v = vulture.Vulture() - v.scavenge(['.']) - assert not v.get_unused_code() - # https://stackoverflow.com/a/59564370/7743427 - - def test_mypy(self): - assert mypy.api.run(['.']) == ('Success: no issues found in 19 source files\n', '', 0) - # https://mypy.readthedocs.io/en/stable/extending_mypy.html#integrating-mypy-into-another-python-application - - def test_vermin(self): - result = subprocess.run(['vermin', 'hypickle'], stdout=PIPE, stderr=PIPE, universal_newlines=True) - expected_output = """Tips: - - Generic or literal annotations might be in use. If so, try using: --eval-annotations - But check the caveat section: https://github.com/netromdk/vermin#caveats - - You're using potentially backported modules: dataclasses, enum, typing - If so, try using the following for better results: --backport dataclasses --backport enum --backport typing - - Since '# novm' or '# novermin' weren't used, a speedup can be achieved using: --no-parse-comments - (disable using: --no-tips) - - Minimum required versions: 3.8 - Incompatible versions: 2""" - assert ( - [line.strip() for line in expected_output.splitlines()] == - [line.strip() for line in result.stdout.splitlines()] - ) - assert (result.returncode, result.stderr) == (0, '') - - def test_future_annotations(self): - for filename in glob.iglob('**/*.py', recursive=True): - assert filename.endswith(".py") - with open(filename) as file: - first_code_line = next( - (line.rstrip('\n') for line in file.readlines() if is_code_line(line)), None - ) - if filename in (r"hypickle/__init__.py", r"hypickle\__init__.py"): - assert first_code_line is None - else: - assert first_code_line == "from __future__ import annotations" - -# Helpers: - -def is_code_line(line: str) -> bool: - return (bool(line.strip()) and not line.lstrip().startswith(('#', '"""')) and - not line.rstrip().endswith('"""')) + def test_lintception(self): + assert linters.run_linters() == linters.LintResult.SUCCESS if __name__ == '__main__': non_automated_tests()