From 7560f26052ae5d6bd619472a13fe1dec42320aae Mon Sep 17 00:00:00 2001 From: Artyom Semidolin <43622365+Artanias@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:08:37 +0300 Subject: [PATCH] refactor: fixes type annotations and makes the output of the time spent on work checks more human-readable. (#200) Refs: #200. --- .pre-commit-config.yaml | 2 +- Makefile | 2 +- docs/notebooks/utils.py | 7 +- pyproject.toml | 12 ++ src/codeplag/codeplagcli.py | 7 +- src/codeplag/cplag/utils.py | 2 +- src/codeplag/getfeatures.py | 5 +- src/codeplag/handlers/check.py | 11 +- src/codeplag/handlers/report.py | 2 +- src/codeplag/logger.py | 2 +- src/codeplag/pyplag/utils.py | 3 +- src/codeplag/types.py | 4 +- test/auto/functional/test_check.py | 5 +- test/auto/functional/test_report.py | 5 +- test/auto/functional/test_settings.py | 3 +- test/conftest.py | 3 +- test/unit/codeplag/algorithms/test_compare.py | 1 + .../codeplag/algorithms/test_featurebased.py | 1 + test/unit/codeplag/conftest.py | 1 + test/unit/codeplag/cplag/test_tree.py | 19 +-- test/unit/codeplag/data/test1.py | 2 +- test/unit/codeplag/data/test2.py | 2 +- test/unit/codeplag/data/test3.py | 4 +- test/unit/codeplag/handlers/test_check.py | 3 +- test/unit/codeplag/handlers/test_report.py | 1 + test/unit/codeplag/pyplag/data/test1.py | 2 +- test/unit/codeplag/pyplag/data/test2.py | 2 +- test/unit/codeplag/pyplag/test_utils.py | 1 + test/unit/codeplag/test_codeplagcli.py | 61 ++++----- test/unit/codeplag/test_config.py | 5 +- test/unit/codeplag/test_display.py | 1 + test/unit/codeplag/test_getfeatures.py | 15 ++- test/unit/codeplag/test_reporters.py | 3 +- test/unit/webparsers/test_github_parser.py | 40 +++--- test/unit/webparsers/test_types.py | 123 ++++++++++-------- 35 files changed, 202 insertions(+), 160 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39aa71d..0cd6c94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.6.4 hooks: - id: ruff args: [ --fix ] diff --git a/Makefile b/Makefile index 9f9f976..3120f61 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -UTIL_VERSION := 0.5.2 +UTIL_VERSION := 0.5.3 UTIL_NAME := codeplag PWD := $(shell pwd) diff --git a/docs/notebooks/utils.py b/docs/notebooks/utils.py index 8fe4c12..b92ec64 100644 --- a/docs/notebooks/utils.py +++ b/docs/notebooks/utils.py @@ -7,12 +7,13 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +from decouple import Config, RepositoryEnv +from scipy.optimize import curve_fit + from codeplag.algorithms.featurebased import counter_metric, struct_compare from codeplag.algorithms.stringbased import gst from codeplag.algorithms.tokenbased import value_jakkar_coef from codeplag.pyplag.utils import get_ast_from_content, get_features_from_ast -from decouple import Config, RepositoryEnv -from scipy.optimize import curve_fit from webparsers.github_parser import GitHubParser @@ -188,7 +189,7 @@ def plot_and_save_result( def get_time_algorithms( df: pd.DataFrame, - work, + work: pd.Series, iterations: int = 5, metric: Literal["fast", "gst", "structure"] = "fast", ) -> pd.DataFrame: diff --git a/pyproject.toml b/pyproject.toml index 851a5dc..0681b4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,19 @@ lint.select = [ "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions + "ANN", # flake8-annotations "SIM", # flake8-simplify "ERA", # eradicate "C90", # mccabe ] lint.ignore = [ + "ANN003", # missing type annotations for `*kwargs` + "ANN101", + "ANN102", + "ANN201", + "ANN204", + "ANN206", + "ANN401", # dynamically typed expressions (typing.Any) "D100", "D101", "D102", @@ -25,6 +33,7 @@ lint.ignore = [ "D105", "D107", ] +lint.exclude = ["*.ipynb"] [tool.ruff.lint.pydocstyle] convention = "google" @@ -32,6 +41,9 @@ convention = "google" [tool.ruff.lint.mccabe] max-complexity = 12 +[tool.ruff.format] +exclude = ["*.ipynb"] + [tool.pyright] pythonVersion = "3.10" diff --git a/src/codeplag/codeplagcli.py b/src/codeplag/codeplagcli.py index 6a6883a..1b9d675 100644 --- a/src/codeplag/codeplagcli.py +++ b/src/codeplag/codeplagcli.py @@ -6,8 +6,6 @@ import builtins from pathlib import Path -from webparsers.types import GitHubContentUrl - from codeplag.consts import ( DEFAULT_MODE, DEFAULT_REPORT_TYPE, @@ -22,6 +20,7 @@ WORKERS_CHOICE, ) from codeplag.types import Settings +from webparsers.types import GitHubContentUrl # FIXME: dirty hook for using logic without translations builtins.__dict__["_"] = builtins.__dict__.get("_", str) @@ -51,7 +50,7 @@ def __call__( class DirPath(Path): """Raises `argparse.ArgumentTypeError` if the directory doesn't exist.""" - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: str, **kwargs): path = Path(*args, **kwargs).resolve() if not path.is_dir(): raise argparse.ArgumentTypeError( @@ -64,7 +63,7 @@ def __new__(cls, *args, **kwargs): class FilePath(Path): """Raises `argparse.ArgumentTypeError` if the file doesn't exist.""" - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: str, **kwargs): path = Path(*args, **kwargs).resolve() if not path.is_file(): raise argparse.ArgumentTypeError( diff --git a/src/codeplag/cplag/utils.py b/src/codeplag/cplag/utils.py index ed10a79..28da018 100644 --- a/src/codeplag/cplag/utils.py +++ b/src/codeplag/cplag/utils.py @@ -4,7 +4,6 @@ from typing import Final from clang.cindex import Config, Cursor, Index, TranslationUnit -from webparsers.types import WorkInfo from codeplag.consts import FILE_DOWNLOAD_PATH, GET_FRAZE, SUPPORTED_EXTENSIONS from codeplag.cplag.const import COMPILE_ARGS @@ -12,6 +11,7 @@ from codeplag.getfeatures import AbstractGetter, get_files_path_from_directory from codeplag.logger import log_err from codeplag.types import ASTFeatures +from webparsers.types import WorkInfo # FIXME: Dirty hook for finding libclang so file LIBCLANG_SO_FILE_PATH: Final[Path] = Path("/usr/lib/llvm-14/lib/libclang-14.so.1") diff --git a/src/codeplag/getfeatures.py b/src/codeplag/getfeatures.py index c7780fd..834fb18 100644 --- a/src/codeplag/getfeatures.py +++ b/src/codeplag/getfeatures.py @@ -6,15 +6,14 @@ from pathlib import Path from typing import Callable, Literal, ParamSpec, overload -from webparsers.github_parser import GitHubParser -from webparsers.types import WorkInfo - from codeplag.consts import ( ALL_EXTENSIONS, GET_FRAZE, UTIL_NAME, ) from codeplag.types import ASTFeatures, Extension, Extensions +from webparsers.github_parser import GitHubParser +from webparsers.types import WorkInfo def get_files_path_from_directory( diff --git a/src/codeplag/handlers/check.py b/src/codeplag/handlers/check.py index 73b6751..e34614b 100644 --- a/src/codeplag/handlers/check.py +++ b/src/codeplag/handlers/check.py @@ -4,6 +4,7 @@ import math import os from concurrent.futures import Future, ProcessPoolExecutor +from datetime import timedelta from itertools import combinations from pathlib import Path from time import monotonic @@ -13,7 +14,6 @@ from decouple import Config, RepositoryEnv from numpy.typing import NDArray from requests import Session -from webparsers.github_parser import GitHubParser from codeplag.algorithms.compare import compare_works from codeplag.config import read_settings_conf @@ -38,6 +38,7 @@ ProcessingWorksInfo, Threshold, ) +from webparsers.github_parser import GitHubParser class WorksComparator: @@ -163,7 +164,7 @@ def check( github_project_folders, github_user, ) - logger.debug("Time for all %.2fs.", monotonic() - begin_time) + logger.debug("Time for all %s.", timedelta(seconds=monotonic() - begin_time)) logger.info("Ending searching for plagiarism ...") if isinstance(self.reporter, CSVReporter): self.reporter._write_df_to_fs() @@ -175,7 +176,7 @@ def __many_to_many_check( features_from_gh_files: list[ASTFeatures], github_project_folders: list[str], github_user: str, - ): + ) -> None: works: list[ASTFeatures] = [] works.extend(features_from_files) works.extend(self.features_getter.get_from_dirs(directories)) @@ -208,7 +209,7 @@ def __one_to_one_check( features_from_gh_files: list[ASTFeatures], github_project_folders: list[str], github_user: str, - ): + ) -> None: combined_elements = filter( bool, ( @@ -304,7 +305,7 @@ def _handle_compare_result( def _handle_completed_futures( self, processing: list[ProcessingWorksInfo], - ): + ) -> None: for proc_works_info in processing: metrics: CompareInfo = proc_works_info.compare_future.result() self._handle_compare_result( diff --git a/src/codeplag/handlers/report.py b/src/codeplag/handlers/report.py index e07837b..bf85089 100644 --- a/src/codeplag/handlers/report.py +++ b/src/codeplag/handlers/report.py @@ -244,7 +244,7 @@ def _create_report( environment: jinja2.Environment, threshold: int = DEFAULT_THRESHOLD, language: Language = DEFAULT_LANGUAGE, -): +) -> None: template = environment.from_string(GENERAL_TEMPLATE_PATH.read_text()) if save_path.is_dir(): save_path = save_path / DEFAULT_GENERAL_REPORT_NAME diff --git a/src/codeplag/logger.py b/src/codeplag/logger.py index 1959402..9098e57 100644 --- a/src/codeplag/logger.py +++ b/src/codeplag/logger.py @@ -81,7 +81,7 @@ def set_handlers( logger.addHandler(get_stderr_handler()) -def log_err(*msgs) -> None: +def log_err(*msgs: str) -> None: for msg in msgs: codeplag_logger.error(msg) diff --git a/src/codeplag/pyplag/utils.py b/src/codeplag/pyplag/utils.py index 65ba10e..5302bf8 100644 --- a/src/codeplag/pyplag/utils.py +++ b/src/codeplag/pyplag/utils.py @@ -2,8 +2,6 @@ import logging from pathlib import Path -from webparsers.types import WorkInfo - from codeplag.consts import GET_FRAZE, SUPPORTED_EXTENSIONS from codeplag.display import red_bold from codeplag.getfeatures import ( @@ -15,6 +13,7 @@ from codeplag.logger import log_err from codeplag.pyplag.astwalkers import ASTWalker from codeplag.types import ASTFeatures +from webparsers.types import WorkInfo def get_ast_from_content(code: str, path: Path | str) -> ast.Module | None: diff --git a/src/codeplag/types.py b/src/codeplag/types.py index 8e71fb1..802879a 100644 --- a/src/codeplag/types.py +++ b/src/codeplag/types.py @@ -93,12 +93,12 @@ def __post_init__(self) -> None: else: self.modify_date = "" - def __eq__(self, other) -> bool: + def __eq__(self, other: "ASTFeatures") -> bool: if not isinstance(other, self.__class__): raise NotImplementedError return str(self.filepath) == str(other.filepath) - def __lt__(self, other) -> bool: + def __lt__(self, other: "ASTFeatures") -> bool: if not isinstance(other, self.__class__): raise NotImplementedError return str(self.filepath) < str(other.filepath) diff --git a/test/auto/functional/test_check.py b/test/auto/functional/test_check.py index 4b5b82a..7cdb671 100644 --- a/test/auto/functional/test_check.py +++ b/test/auto/functional/test_check.py @@ -5,11 +5,12 @@ import re import pytest -from codeplag.consts import CONFIG_PATH, UTIL_NAME, UTIL_VERSION -from codeplag.types import WorksReport from const import REPORTS_FOLDER from utils import modify_settings, run_check, run_util +from codeplag.consts import CONFIG_PATH, UTIL_NAME, UTIL_VERSION +from codeplag.types import WorksReport + CWD = os.getcwd() CPP_FILES = [ "test/unit/codeplag/cplag/data/sample1.cpp", diff --git a/test/auto/functional/test_report.py b/test/auto/functional/test_report.py index 9d61a31..2c35b4a 100644 --- a/test/auto/functional/test_report.py +++ b/test/auto/functional/test_report.py @@ -1,11 +1,12 @@ from pathlib import Path import pytest -from codeplag.consts import DEFAULT_GENERAL_REPORT_NAME, DEFAULT_SOURCES_REPORT_NAME -from codeplag.types import ReportType from const import REPORTS_FOLDER from utils import create_report, modify_settings, run_check +from codeplag.consts import DEFAULT_GENERAL_REPORT_NAME, DEFAULT_SOURCES_REPORT_NAME +from codeplag.types import ReportType + @pytest.fixture(scope="function", autouse=True) def setup(create_reports_folder: None): diff --git a/test/auto/functional/test_settings.py b/test/auto/functional/test_settings.py index f74138b..a782d6f 100644 --- a/test/auto/functional/test_settings.py +++ b/test/auto/functional/test_settings.py @@ -4,9 +4,10 @@ from typing import Literal import pytest +from utils import modify_settings + from codeplag.consts import CONFIG_PATH, UTIL_NAME from codeplag.types import Language, LogLevel, ReportsExtension, Threshold -from utils import modify_settings @pytest.fixture(scope="module", autouse=True) diff --git a/test/conftest.py b/test/conftest.py index 30beb34..df9947c 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,9 +1,10 @@ import logging import pytest -from codeplag.consts import UTIL_NAME from pytest_mock import MockerFixture +from codeplag.consts import UTIL_NAME + @pytest.fixture def dummy_logger(mocker: MockerFixture): diff --git a/test/unit/codeplag/algorithms/test_compare.py b/test/unit/codeplag/algorithms/test_compare.py index 3705a2d..fa90473 100644 --- a/test/unit/codeplag/algorithms/test_compare.py +++ b/test/unit/codeplag/algorithms/test_compare.py @@ -1,4 +1,5 @@ import pytest + from codeplag.algorithms.compare import compare_works, fast_compare from codeplag.types import ASTFeatures, CompareInfo diff --git a/test/unit/codeplag/algorithms/test_featurebased.py b/test/unit/codeplag/algorithms/test_featurebased.py index ee2220e..f27094f 100644 --- a/test/unit/codeplag/algorithms/test_featurebased.py +++ b/test/unit/codeplag/algorithms/test_featurebased.py @@ -2,6 +2,7 @@ import unittest import numpy as np + from codeplag.algorithms.featurebased import ( add_not_counted, counter_metric, diff --git a/test/unit/codeplag/conftest.py b/test/unit/codeplag/conftest.py index da63069..a3b4970 100644 --- a/test/unit/codeplag/conftest.py +++ b/test/unit/codeplag/conftest.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest + from codeplag.algorithms.compare import compare_works from codeplag.pyplag.utils import get_ast_from_filename, get_features_from_ast from codeplag.types import ASTFeatures, CompareInfo diff --git a/test/unit/codeplag/cplag/test_tree.py b/test/unit/codeplag/cplag/test_tree.py index 4ddbfdb..9b7125b 100644 --- a/test/unit/codeplag/cplag/test_tree.py +++ b/test/unit/codeplag/cplag/test_tree.py @@ -3,7 +3,8 @@ from typing import Final import pytest -from clang.cindex import CursorKind +from clang.cindex import Cursor, CursorKind + from codeplag.cplag.tree import generic_visit, get_features, get_not_ignored from codeplag.cplag.utils import get_cursor_from_file from codeplag.types import ASTFeatures @@ -16,13 +17,13 @@ @pytest.fixture(scope='module', autouse=True) -def setup(): +def setup() -> None: assert _SAMPLE1_PATH.exists() and _SAMPLE2_PATH.exists() assert _SAMPLE3_PATH.exists() and _SAMPLE4_PATH.exists() @pytest.fixture() -def first_cursor(): +def first_cursor() -> Cursor: cursor = get_cursor_from_file(_SAMPLE1_PATH) assert cursor is not None @@ -30,7 +31,7 @@ def first_cursor(): @pytest.fixture() -def second_cursor(): +def second_cursor() -> Cursor: cursor = get_cursor_from_file(_SAMPLE2_PATH) assert cursor is not None @@ -38,14 +39,14 @@ def second_cursor(): @pytest.fixture() -def third_cursor(): +def third_cursor() -> Cursor: cursor = get_cursor_from_file(_SAMPLE3_PATH) assert cursor is not None return cursor -def test_get_not_ignored_normal(first_cursor, second_cursor): +def test_get_not_ignored_normal(first_cursor: Cursor, second_cursor: Cursor) -> None: res1 = get_not_ignored(first_cursor, _SAMPLE1_PATH) res2 = get_not_ignored(second_cursor, _SAMPLE2_PATH) @@ -83,7 +84,7 @@ def test_get_not_ignored_normal(first_cursor, second_cursor): assert len(res2) == 1 -def test_generic_visit(first_cursor): +def test_generic_visit(first_cursor: Cursor) -> None: features = ASTFeatures(_SAMPLE1_PATH) generic_visit(first_cursor, features) @@ -103,7 +104,7 @@ def test_generic_visit(first_cursor): 100, 101, 100, 101] -def test_get_features(second_cursor): +def test_get_features(second_cursor: Cursor) -> None: features = get_features(second_cursor, _SAMPLE2_PATH) assert features.filepath == _SAMPLE2_PATH @@ -123,7 +124,7 @@ def test_get_features(second_cursor): assert features.sha256 == "957da24c9f9340954aaa9df303922a0fbdcea69b6d3556c2bd462d195ab89052" -def test_bad_encoding_syms(third_cursor): +def test_bad_encoding_syms(third_cursor: Cursor) -> None: features = get_features(third_cursor, _SAMPLE3_PATH) assert features.filepath == _SAMPLE3_PATH diff --git a/test/unit/codeplag/data/test1.py b/test/unit/codeplag/data/test1.py index c733295..8a744ae 100644 --- a/test/unit/codeplag/data/test1.py +++ b/test/unit/codeplag/data/test1.py @@ -1,4 +1,4 @@ -def my_func(length, future): +def my_func(length, future): # noqa: ANN001 future += length print(future) return future diff --git a/test/unit/codeplag/data/test2.py b/test/unit/codeplag/data/test2.py index 3ced3e7..94a8b6c 100644 --- a/test/unit/codeplag/data/test2.py +++ b/test/unit/codeplag/data/test2.py @@ -1,4 +1,4 @@ -def my_func(length, future, shifr): +def my_func(length, future, shifr): # noqa: ANN001 future += length print(shifr * future) return future diff --git a/test/unit/codeplag/data/test3.py b/test/unit/codeplag/data/test3.py index 1250127..80d9cc9 100644 --- a/test/unit/codeplag/data/test3.py +++ b/test/unit/codeplag/data/test3.py @@ -1,13 +1,13 @@ import os -def join_path(path): +def join_path(path): # noqa: ANN001 cwd = os.getcwd() return os.path.join(cwd, path) -def example(new_path, is_exist): +def example(new_path, is_exist): # noqa: ANN001 if is_exist: new_path = join_path(new_path) diff --git a/test/unit/codeplag/handlers/test_check.py b/test/unit/codeplag/handlers/test_check.py index 30063bb..d1ac997 100644 --- a/test/unit/codeplag/handlers/test_check.py +++ b/test/unit/codeplag/handlers/test_check.py @@ -1,12 +1,13 @@ import numpy as np import pandas as pd import pytest +from pandas.testing import assert_frame_equal + from codeplag.handlers.check import ( _calc_iterations, compliance_matrix_to_df, ) from codeplag.types import Mode -from pandas.testing import assert_frame_equal @pytest.mark.parametrize( diff --git a/test/unit/codeplag/handlers/test_report.py b/test/unit/codeplag/handlers/test_report.py index 85e1545..4fe68ec 100644 --- a/test/unit/codeplag/handlers/test_report.py +++ b/test/unit/codeplag/handlers/test_report.py @@ -2,6 +2,7 @@ import numpy as np import pytest + from codeplag.handlers.report import ( CntHeadNodes, ResultingSamePercentages, diff --git a/test/unit/codeplag/pyplag/data/test1.py b/test/unit/codeplag/pyplag/data/test1.py index 0b099dc..689fce4 100644 --- a/test/unit/codeplag/pyplag/data/test1.py +++ b/test/unit/codeplag/pyplag/data/test1.py @@ -3,7 +3,7 @@ FLOAT_CONST = 10.5 -def my_func(length, future): +def my_func(length, future): # noqa: ANN001 future += length print(future) return future diff --git a/test/unit/codeplag/pyplag/data/test2.py b/test/unit/codeplag/pyplag/data/test2.py index 3ced3e7..94a8b6c 100644 --- a/test/unit/codeplag/pyplag/data/test2.py +++ b/test/unit/codeplag/pyplag/data/test2.py @@ -1,4 +1,4 @@ -def my_func(length, future, shifr): +def my_func(length, future, shifr): # noqa: ANN001 future += length print(shifr * future) return future diff --git a/test/unit/codeplag/pyplag/test_utils.py b/test/unit/codeplag/pyplag/test_utils.py index bd3fc55..c571df8 100644 --- a/test/unit/codeplag/pyplag/test_utils.py +++ b/test/unit/codeplag/pyplag/test_utils.py @@ -3,6 +3,7 @@ from typing import Final import numpy as np + from codeplag.pyplag.utils import get_ast_from_filename, get_features_from_ast CWD: Final[Path] = Path(os.path.dirname(os.path.abspath(__file__))) diff --git a/test/unit/codeplag/test_codeplagcli.py b/test/unit/codeplag/test_codeplagcli.py index 8191ce3..34a47e1 100644 --- a/test/unit/codeplag/test_codeplagcli.py +++ b/test/unit/codeplag/test_codeplagcli.py @@ -5,6 +5,7 @@ from typing import Final import pytest + from codeplag.codeplagcli import CodeplagCLI, DirPath, FilePath CWD: Final[Path] = Path(os.getcwd()) @@ -30,7 +31,7 @@ def test_dir_path(path: str, expected: Path): @pytest.mark.parametrize("path", ["bad_dirpath", "Makefile"]) -def test_dir_path_bad(path): +def test_dir_path_bad(path: str): with pytest.raises(argparse.ArgumentTypeError): DirPath(path) @@ -45,50 +46,38 @@ def test_file_path(path: str, expected: Path): @pytest.mark.parametrize( "path", [ - ("./src"), - ("./profile.d"), - ("bad_filepath"), + "./src", + "./profile.d", + "bad_filepath", ], ) -def test_file_path_bad(path): +def test_file_path_bad(path: str): with pytest.raises(argparse.ArgumentTypeError): FilePath(path) @pytest.mark.parametrize( - "args, raises", + "args", [ - ( - ["--extension", "py", "--directories", "src/", "src/"], - pytest.raises(SystemExit), - ), - ( - [ - "--extension", - "py", - "--github-project-folders", - "https://github.com/OSLL/code-plagiarism/tree/main/src", - "https://github.com/OSLL/code-plagiarism/tree/main/src", - ], - pytest.raises(SystemExit), - ), - ( - [ - "--extension", - "py", - "--github-files", - "https://github.com/OSLL/code-plagiarism/blob/main/setup.py", - "https://github.com/OSLL/code-plagiarism/blob/main/setup.py", - ], - pytest.raises(SystemExit), - ), - ( - ["--extension", "py", "--files", "setup.py", "setup.py"], - pytest.raises(SystemExit), - ), + ["--extension", "py", "--directories", "src/", "src/"], + [ + "--extension", + "py", + "--github-project-folders", + "https://github.com/OSLL/code-plagiarism/tree/main/src", + "https://github.com/OSLL/code-plagiarism/tree/main/src", + ], + [ + "--extension", + "py", + "--github-files", + "https://github.com/OSLL/code-plagiarism/blob/main/setup.py", + "https://github.com/OSLL/code-plagiarism/blob/main/setup.py", + ], + ["--extension", "py", "--files", "setup.py", "setup.py"], ], ) -def test_get_parsed_args(args, raises): +def test_get_parsed_args(args: list[str]): codeplagcli = CodeplagCLI() - with raises: + with pytest.raises(SystemExit): codeplagcli.parse_args(args=args) diff --git a/test/unit/codeplag/test_config.py b/test/unit/codeplag/test_config.py index 07dfd35..8836dad 100644 --- a/test/unit/codeplag/test_config.py +++ b/test/unit/codeplag/test_config.py @@ -5,10 +5,11 @@ from unittest.mock import MagicMock import pytest +from pytest_mock import MockerFixture + from codeplag import config from codeplag.consts import CONFIG_PATH, UTIL_NAME from codeplag.types import Settings -from pytest_mock import MockerFixture config.logger = MagicMock(autospec=logging.Logger(UTIL_NAME)) @@ -31,7 +32,7 @@ def path(mocker: MockerFixture) -> MagicMock: @pytest.fixture -def settings_config(request, mocker: MockerFixture) -> Settings | None: +def settings_config(request: pytest.FixtureRequest, mocker: MockerFixture) -> Settings | None: mocker.patch.object(config, "read_config", return_value=request.param) return request.param diff --git a/test/unit/codeplag/test_display.py b/test/unit/codeplag/test_display.py index 008b2f4..2e87339 100644 --- a/test/unit/codeplag/test_display.py +++ b/test/unit/codeplag/test_display.py @@ -1,4 +1,5 @@ import pytest + from codeplag.display import ComplexProgress, Progress diff --git a/test/unit/codeplag/test_getfeatures.py b/test/unit/codeplag/test_getfeatures.py index b342ac4..4e8e130 100644 --- a/test/unit/codeplag/test_getfeatures.py +++ b/test/unit/codeplag/test_getfeatures.py @@ -1,14 +1,16 @@ import re from pathlib import Path +from unittest.mock import MagicMock import pytest -from codeplag.getfeatures import get_files_path_from_directory, set_sha256 -from codeplag.types import ASTFeatures from pytest_mock import MockerFixture +from codeplag.getfeatures import get_files_path_from_directory, set_sha256 +from codeplag.types import ASTFeatures, Extensions + @pytest.fixture -def os_walk(request, mocker: MockerFixture): +def os_walk(request: pytest.FixtureRequest, mocker: MockerFixture) -> MagicMock: return mocker.patch("os.walk", return_value=request.param) @@ -52,7 +54,12 @@ def os_walk(request, mocker: MockerFixture): ], indirect=["os_walk"], ) -def test_get_files_path_from_directory(os_walk, extensions, path_regexp, expected): +def test_get_files_path_from_directory( + os_walk: MagicMock, + extensions: Extensions | None, + path_regexp: re.Pattern | None, + expected: list[Path], +): files = get_files_path_from_directory( Path("./"), path_regexp=path_regexp, extensions=extensions ) diff --git a/test/unit/codeplag/test_reporters.py b/test/unit/codeplag/test_reporters.py index 3e81d26..cc9b4cd 100644 --- a/test/unit/codeplag/test_reporters.py +++ b/test/unit/codeplag/test_reporters.py @@ -3,6 +3,8 @@ import pandas as pd import pytest +from pytest_mock import MockerFixture + from codeplag.consts import CSV_REPORT_COLUMNS, CSV_REPORT_FILENAME from codeplag.handlers.report import deserialize_compare_result from codeplag.reporters import CSVReporter, JSONReporter @@ -11,7 +13,6 @@ CompareInfo, WorksReport, ) -from pytest_mock import MockerFixture @pytest.fixture diff --git a/test/unit/webparsers/test_github_parser.py b/test/unit/webparsers/test_github_parser.py index fd2fbe2..7b16a7c 100644 --- a/test/unit/webparsers/test_github_parser.py +++ b/test/unit/webparsers/test_github_parser.py @@ -4,7 +4,7 @@ import unittest from contextlib import redirect_stdout from typing import Final -from unittest.mock import call, patch +from unittest.mock import MagicMock, call, patch from webparsers.github_parser import GitHubParser from webparsers.types import Branch, Commit, PullRequest, Repository, WorkInfo @@ -110,7 +110,7 @@ def test__is_accepted_extension(self): self.assertEqual(rv, test_case["expected_result"]) @patch("webparsers.github_parser.requests.get") - def test_send_get_request(self, mock_get): + def test_send_get_request(self, mock_get: MagicMock): test_cases = [ { "arguments": {"api_url": "users/moevm/repos", "params": {}}, @@ -138,7 +138,7 @@ def test_send_get_request(self, mock_get): ) @patch("webparsers.github_parser.requests.get") - def test_send_get_request_bad(self, mock_get): + def test_send_get_request_bad(self, mock_get: MagicMock): test_cases = [ { "arguments": {"api_url": "Test/url", "params": {}}, @@ -204,7 +204,7 @@ def test_send_get_request_bad(self, mock_get): ) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_list_of_repos(self, mock_send_get_request): + def test_get_list_of_repos(self, mock_send_get_request: MagicMock): test_cases = [ { "arguments": {"owner": "OSLL", "reg_exp": None}, @@ -305,7 +305,7 @@ def test_get_list_of_repos(self, mock_send_get_request): self.assertEqual(mock_send_get_request.mock_calls, test_case["send_calls"]) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_list_of_repos_bad(self, mock_send_get_request): + def test_get_list_of_repos_bad(self, mock_send_get_request: MagicMock): test_cases = [ { "arguments": {"owner": "OSLL", "reg_exp": None}, @@ -327,7 +327,7 @@ def test_get_list_of_repos_bad(self, mock_send_get_request): self.assertEqual(mock_send_get_request.mock_calls, test_case["send_calls"]) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_pulls_info(self, mock_send_get_request): + def test_get_pulls_info(self, mock_send_get_request: MagicMock): test_cases = [ { "arguments": {"owner": "OSLL", "repo": "code-plagiarism"}, @@ -450,7 +450,7 @@ def test_get_pulls_info(self, mock_send_get_request): assert actual_call == expected_call @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_name_default_branch(self, mock_send_get_request): + def test_get_name_default_branch(self, mock_send_get_request: MagicMock): test_cases = [ { "arguments": {"owner": "OSLL", "repo": "aido-auto-feedback"}, @@ -478,7 +478,7 @@ def test_get_name_default_branch(self, mock_send_get_request): self.assertEqual(mock_send_get_request.mock_calls, test_case["send_calls"]) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test__get_branch_last_commit_info(self, mock_send_get_request): + def test__get_branch_last_commit_info(self, mock_send_get_request: MagicMock): _COMMIT1 = { "sha": "jal934304", "commit": {"author": {"name": "OSLL", "date": "2022-12-29T10:10:41Z"}}, @@ -519,7 +519,7 @@ def test__get_branch_last_commit_info(self, mock_send_get_request): self.assertEqual(mock_send_get_request.mock_calls, test_case["send_calls"]) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_file_content_by_sha(self, mock_send_get_request): + def test_get_file_content_by_sha(self, mock_send_get_request: MagicMock): test_cases = [ { "arguments": { @@ -564,7 +564,7 @@ def test_get_file_content_by_sha(self, mock_send_get_request): @patch("webparsers.github_parser.GitHubParser.get_file_content_by_sha") @patch("webparsers.github_parser.GitHubParser.send_get_request") def test_get_files_generator_from_sha_commit( - self, mock_send_get_request, mock_get_file_content_by_sha + self, mock_send_get_request: MagicMock, mock_get_file_content_by_sha: MagicMock ): test_cases = [ { @@ -793,7 +793,7 @@ def test_get_files_generator_from_sha_commit( ) @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_list_repo_branches(self, mock_send_get_request): + def test_get_list_repo_branches(self, mock_send_get_request: MagicMock): _COMMIT_DATE = "2022-12-29T10:10:41Z" _BRANCH_INFO1 = { "name": "main", @@ -860,10 +860,10 @@ def test_get_list_repo_branches(self, mock_send_get_request): @patch("webparsers.github_parser.GitHubParser.get_files_generator_from_sha_commit") def test_get_files_generator_from_repo_url( self, - mock_get_files_generator_from_sha_commit, - mock_get_sha_last_branch_commit, - mock_get_list_repo_branches, - mock_get_name_default_branch, + mock_get_files_generator_from_sha_commit: MagicMock, + mock_get_sha_last_branch_commit: MagicMock, + mock_get_list_repo_branches: MagicMock, + mock_get_name_default_branch: MagicMock, ): test_cases = [ { @@ -911,7 +911,9 @@ def test_get_files_generator_from_repo_url( @patch("webparsers.github_parser.GitHubParser.get_file_content_by_sha") @patch("webparsers.github_parser.GitHubParser.send_get_request") - def test_get_file_from_url(self, mock_send_get_request, mock_get_file_content_by_sha): + def test_get_file_from_url( + self, mock_send_get_request: MagicMock, mock_get_file_content_by_sha: MagicMock + ): test_cases = [ { "arguments": { @@ -940,9 +942,9 @@ def test_get_file_from_url(self, mock_send_get_request, mock_get_file_content_by @patch("webparsers.github_parser.GitHubParser.send_get_request") def test_get_files_generator_from_dir_url( self, - mock_send_get_request, - mock_get_files_generator_from_sha_commit, - mock_get_file_content_by_sha, + mock_send_get_request: MagicMock, + mock_get_files_generator_from_sha_commit: MagicMock, + mock_get_file_content_by_sha: MagicMock, ): test_cases = [ { diff --git a/test/unit/webparsers/test_types.py b/test/unit/webparsers/test_types.py index a0d981d..697924e 100644 --- a/test/unit/webparsers/test_types.py +++ b/test/unit/webparsers/test_types.py @@ -1,34 +1,41 @@ import pytest + from webparsers.types import GitHubContentUrl, GitHubRepoUrl, GitHubUrl @pytest.mark.parametrize( - "arg, expected", + "url, expected", [ ("https://github.com/OSLL", ["https:", "", "github.com", "OSLL"]), ( "http://github.com/OSLL/code-plagiarism/", ["http:", "", "github.com", "OSLL", "code-plagiarism"], ), - ("ttps://github.com/OSLL", ValueError), - ("https:/j/github.com/OSLL", ValueError), - ("https://github.com/OSLL", ValueError), - ("https:/", ValueError), ], ) -def test_github_url(arg, expected): - if type(expected) == type and issubclass(expected, Exception): - with pytest.raises(expected): - GitHubUrl(arg) - else: - gh_url = GitHubUrl(arg) - assert gh_url.url_parts == expected - assert gh_url.protocol == expected[0][:-1] - assert gh_url.host == expected[2] +def test_github_url(url: str, expected: list[str]) -> None: + gh_url = GitHubUrl(url) + assert gh_url.url_parts == expected + assert gh_url.protocol == expected[0][:-1] + assert gh_url.host == expected[2] + + +@pytest.mark.parametrize( + "url", + [ + "ttps://github.com/OSLL", + "https:/j/github.com/OSLL", + "https://github.com/OSLL", + "https:/", + ], +) +def test_github_url_bad(url: str) -> None: + with pytest.raises(ValueError): + GitHubUrl(url) @pytest.mark.parametrize( - "arg, expected", + "url, expected", [ ( "http://github.com/OSLL/code-plagiarism/", @@ -38,28 +45,34 @@ def test_github_url(arg, expected): "http://github.com/OSLL/samples", ["http:", "", "github.com", "OSLL", "samples"], ), - ("https://github.com/OSLL", ValueError), - ("ttps://github.com/OSLL", ValueError), - ("https:/j/github.com/OSLL", ValueError), - ("https://github.com/OSLL", ValueError), - ("https:/", ValueError), ], ) -def test_github_repo_url(arg, expected): - if type(expected) == type and issubclass(expected, Exception): - with pytest.raises(expected): - GitHubRepoUrl(arg) - else: - gh_repo_url = GitHubRepoUrl(arg) - assert gh_repo_url.url_parts == expected - assert gh_repo_url.protocol == expected[0][:-1] - assert gh_repo_url.host == expected[2] - assert gh_repo_url.owner == expected[3] - assert gh_repo_url.repo == expected[4] +def test_github_repo_url(url: str, expected: list[str]) -> None: + gh_repo_url = GitHubRepoUrl(url) + assert gh_repo_url.url_parts == expected + assert gh_repo_url.protocol == expected[0][:-1] + assert gh_repo_url.host == expected[2] + assert gh_repo_url.owner == expected[3] + assert gh_repo_url.repo == expected[4] @pytest.mark.parametrize( - "arg, expected, path", + "url", + [ + "https://github.com/OSLL", + "ttps://github.com/OSLL", + "https:/j/github.com/OSLL", + "https://github.com/OSLL", + "https:/", + ], +) +def test_github_repo_url_bad(url: str) -> None: + with pytest.raises(ValueError): + GitHubRepoUrl(url) + + +@pytest.mark.parametrize( + "url, expected, path", [ ( "https://github.com/OSLL/code-plagiarism/blob/main/src/codeplag/astfeatures.py", @@ -71,25 +84,31 @@ def test_github_repo_url(arg, expected): ["https:", "", "github.com", "OSLL", "code-plagiarism", "blob", "main"], "src/codeplag/logger.py", ), - ("https://github.com/OSLL", ValueError, ""), - ("http://github.com/OSLL/code-plagiarism/", ValueError, ""), - ("ttps://github.com/OSLL", ValueError, ""), - ("https:/j/github.com/OSLL", ValueError, ""), - ("https://github.com/OSLL", ValueError, ""), - ("https:/", ValueError, ""), ], ) -def test_github_content_url(arg, expected, path): - if type(expected) == type and issubclass(expected, Exception): - with pytest.raises(expected): - GitHubContentUrl(arg) - else: - gh_content_url = GitHubContentUrl(arg) - for value in expected: - assert value in gh_content_url.url_parts - assert gh_content_url.protocol == expected[0][:-1] - assert gh_content_url.host == expected[2] - assert gh_content_url.owner == expected[3] - assert gh_content_url.repo == expected[4] - assert gh_content_url.branch == expected[6] - assert gh_content_url.path == path +def test_github_content_url(url: str, expected: list[str], path: str) -> None: + gh_content_url = GitHubContentUrl(url) + for value in expected: + assert value in gh_content_url.url_parts + assert gh_content_url.protocol == expected[0][:-1] + assert gh_content_url.host == expected[2] + assert gh_content_url.owner == expected[3] + assert gh_content_url.repo == expected[4] + assert gh_content_url.branch == expected[6] + assert gh_content_url.path == path + + +@pytest.mark.parametrize( + "url", + [ + "https://github.com/OSLL", + "http://github.com/OSLL/code-plagiarism/", + "ttps://github.com/OSLL", + "https:/j/github.com/OSLL", + "https://github.com/OSLL", + "https:/", + ], +) +def test_github_content_url_bad(url: str) -> None: + with pytest.raises(ValueError): + GitHubContentUrl(url)