Skip to content

Commit

Permalink
Merge pull request #65 from nathanjmcdougall/60-add-configuration-to-…
Browse files Browse the repository at this point in the history
…disable-rules

60 add configuration to disable rules
  • Loading branch information
nathanjmcdougall authored Jan 18, 2024
2 parents d349396 + 3d34e7e commit 7323095
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 29 deletions.
2 changes: 2 additions & 0 deletions doc/changelog_entries/60.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Global `ignore` configuration is now supported, allowing you to disable a linting rule
completely across all files.
3 changes: 3 additions & 0 deletions src/suiteas/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from pydantic import BaseModel, model_validator

from suiteas.core.violations import RULE_CODES, RuleCode


class ProjConfig(BaseModel):
"""Configuration for the Python project to be analyzed."""
Expand All @@ -14,6 +16,7 @@ class ProjConfig(BaseModel):
tests_rel_path: Path = Path("tests")
unittest_dir_name: Path = Path("unit")
use_consolidated_tests_dir: bool = False
checks: list[RuleCode] = RULE_CODES

@model_validator(mode="after")
def check_consolidation_consistency(self) -> Self:
Expand Down
113 changes: 84 additions & 29 deletions src/suiteas/core/check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Functionality to check whether a test suite is compliant."""


from pathlib import Path

from pydantic.alias_generators import to_snake

from suiteas.core.names import PYTEST_CLASS_PREFIX
Expand All @@ -11,7 +13,7 @@
missing_test_func,
unimported_tested_func,
)
from suiteas.domain import Func, Project, PytestFile
from suiteas.domain import File, Func, Project, PytestFile


def get_violations(project: Project) -> list[Violation]:
Expand Down Expand Up @@ -39,40 +41,67 @@ def get_violations(project: Project) -> list[Violation]:
if func.is_underscored:
continue

# Check SUI001: missing-test-module
if not _pytest_file_has_func_tests(pytest_file=pytest_file, func=func):
violations.append(
Violation(
rule=missing_test_func,
rel_path=file.path.relative_to(project.proj_dir),
line_num=func.line_num,
char_offset=func.char_offset,
fmt_info=dict(
func=func.name,
pytest_file_rel_posix=pytest_rel_path.as_posix(),
),
if "SUI001" in project.config.checks:
violations.extend(
_get_sui001_violations(
pytest_file=pytest_file,
func=func,
file=file,
project=project,
pytest_rel_path=pytest_rel_path,
),
)

# Check SUI003: unimported-tested-func
if not _pytest_file_imports_func(
pytest_file=pytest_file,
func=func,
):
violations.append(
Violation(
rule=unimported_tested_func,
rel_path=file.path.relative_to(project.proj_dir),
line_num=func.line_num,
char_offset=func.char_offset,
fmt_info=dict(
func_fullname=func.full_name,
pytest_file_rel_posix=pytest_rel_path.as_posix(),
),
if "SUI003" in project.config.checks:
violations.extend(
_get_sui003_violations(
pytest_file=pytest_file,
func=func,
file=file,
project=project,
pytest_rel_path=pytest_rel_path,
),
)

# Check SUI002: empty-pytest-class
if "SUI002" in project.config.checks:
violations.extend(_get_sui002_violations(project=project))

return violations


def _get_sui001_violations(
*,
pytest_file: PytestFile | None,
func: Func,
file: File,
project: Project,
pytest_rel_path: Path,
) -> list[Violation]:
if not _pytest_file_has_func_tests(
pytest_file=pytest_file,
func=func,
):
return [
Violation(
rule=missing_test_func,
rel_path=file.path.relative_to(project.proj_dir),
line_num=func.line_num,
char_offset=func.char_offset,
fmt_info=dict(
func=func.name,
pytest_file_rel_posix=pytest_rel_path.as_posix(),
),
),
]
return []


def _get_sui002_violations(
*,
project: Project,
) -> list[Violation]:
violations = []
for pytest_file in project.pytest_suite.pytest_files:
for pytest_class in pytest_file.pytest_classes:
if not pytest_class.has_funcs:
Expand All @@ -83,10 +112,36 @@ def get_violations(project: Project) -> list[Violation]:
fmt_info=dict(pytest_class_name=pytest_class.name),
),
)

return violations


def _get_sui003_violations(
*,
pytest_file: PytestFile | None,
func: Func,
file: File,
project: Project,
pytest_rel_path: Path,
) -> list[Violation]:
if not _pytest_file_imports_func(
pytest_file=pytest_file,
func=func,
):
return [
Violation(
rule=unimported_tested_func,
rel_path=file.path.relative_to(project.proj_dir),
line_num=func.line_num,
char_offset=func.char_offset,
fmt_info=dict(
func_fullname=func.full_name,
pytest_file_rel_posix=pytest_rel_path.as_posix(),
),
),
]
return []


def _pytest_file_has_func_tests(
*,
pytest_file: PytestFile | None,
Expand Down
6 changes: 6 additions & 0 deletions src/suiteas/core/violations.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"""Utilities for recording violations of the test suite rules."""


import typing
from pathlib import Path
from typing import Literal, TypeAlias

from pydantic import BaseModel

RuleCode: TypeAlias = Literal["SUI001", "SUI002", "SUI003"]

RULE_CODES: list[RuleCode] = list(typing.get_args(RuleCode))


class Rule(BaseModel):
"""A rule enforced by SuiteAs."""
Expand Down
5 changes: 5 additions & 0 deletions src/suiteas/read/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from suiteas.config import ProjConfig
from suiteas.core.names import PYPROJTOML_NAME
from suiteas.core.violations import RULE_CODES, RuleCode


class ConfigFileError(ValueError):
Expand All @@ -30,6 +31,7 @@ class TOMLProjConfig(BaseModel):
unittest_dir_name: Path | None = None
project_name: str | None = None
setuptools_pkg_names: list[str] | None = None
ignore: list[RuleCode] | None = None
model_config = dict(extra="forbid")


Expand Down Expand Up @@ -95,13 +97,16 @@ def get_config(*, proj_dir: Path) -> ProjConfig:
unittests_dir=unittests_dir,
use_consolidated_tests_dir=use_consolidated_tests_dir,
)
checks = list(set(RULE_CODES) - set(toml_config.ignore or []))
checks.sort()

return ProjConfig(
pkg_names=pkg_names,
src_rel_path=src_rel_path,
tests_rel_path=tests_rel_path,
unittest_dir_name=unittest_dir_name,
use_consolidated_tests_dir=use_consolidated_tests_dir,
checks=checks,
)


Expand Down
14 changes: 14 additions & 0 deletions tests/assets/config_files/bad_ignore.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "example"
version = "0.1.0"
description = "Example"

[tool.suiteas]
pkg_names=["foo", "bar"]
src_rel_path = "mysrc"
tests_rel_path = "mytests"
unittest_dir_name = "myunit"
ignore = ["bad"]

[tool.setuptools]
packages = ["foo_other", "bar_other"]
1 change: 1 addition & 0 deletions tests/assets/config_files/complete.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pkg_names=["foo", "bar"]
src_rel_path = "mysrc"
tests_rel_path = "mytests"
unittest_dir_name = "myunit"
ignore = ["SUI002"]

[tool.setuptools]
packages = ["foo_other", "bar_other"]
14 changes: 14 additions & 0 deletions tests/unit/suiteas/read/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def test_complete(self, config_files_parent_dir: Path) -> None:
unittest_dir_name=Path("myunit"),
setuptools_pkg_names=["foo_other", "bar_other"],
project_name="example",
ignore=["SUI002"],
)

def test_syntax_error(self, config_files_parent_dir: Path) -> None:
Expand Down Expand Up @@ -165,3 +166,16 @@ def test_extra_subsection(self, config_files_parent_dir: Path) -> None:
),
):
get_toml_config(config_files_parent_dir / "extra_subsection.toml")

def test_bad_ignore(self, config_files_parent_dir: Path) -> None:
with pytest.raises(
ConfigFileError,
match=(
r"Invalid \[tool.suiteas\] configuration section at .*: "
r"1 validation error for TOMLProjConfig\n"
r"ignore.0\n"
r".* Input should be .* "
r"[type=literal_error, input_value='bad', input_type=str].*"
),
):
get_toml_config(config_files_parent_dir / "bad_ignore.toml")

0 comments on commit 7323095

Please sign in to comment.