testsAndMisc/meta/pyproject.toml
Krzysztof kuhy Rudnicki 6b828d29aa Extract digital-wellbeing; the directory-depth cap now enforces
The last unit over the cap is now github.com/kuhyx/digital-wellbeing, cloned
at ~/digital-wellbeing, and `--warn` is gone from the directory-depth-cap
hook. `check_directory_depth.sh --all` exits 0 with zero violations, down from
46 at the start of the session. That was the point of the campaign.

History was filtered across all three paths the unit has lived at (63
commits); the current path alone holds 2.

The six suites that test this code lived in linux_configuration/tests/ rather
than beside it, so they were filtered separately (24 commits) and merged in as
a second root. A path filter of the unit alone would have left them here,
testing code that had left the repo -- and their names would still have been
in shell-tests.yml's hardcoded arrays, failing CI on missing files.

linux_configuration/lib/common.sh is vendored into vendor/ rather than trimmed
to the four functions actually used: set_actual_user_vars calls two of the
others, so a trim breaks silently. It is in vendor/ and not lib/ because
test_music_parallelism.sh copies the unit's own lib/ into a scratch worktree
while stubbing the shared lib; in one directory the real common.sh overwrote
the stub, and the suite silently measured the wrong library.

Running the callers caught what shellcheck and grep both missed:
check_and_enable_services.sh died with "DW_REPO: unbound variable" because the
definition sat below its first use. It now runs clean and reports ok for every
extracted-unit check.

Two long-standing live defects fixed as a consequence. The pacman-wrapper
drift manifest embeds ABSOLUTE paths and had named deleted scripts/ paths
since the flatten, so the check reported a permanent false "stale or
tampered"; it now verifies, with unchanged hashes proving the extracted bytes
are identical. And the last two stale live references are gone: 8 -> 0.

The two pre-commit excludes for setup_midnight_shutdown.sh are dropped rather
than moved. lsattr shows nothing in the unit is chattr +i any more, and the
fixer hooks leave the file alone when allowed to run.

Removing 80 shell files also shrank the jscpd denominator enough that the
virtualenv/tqdm clones inside the GENERATED .ci-mirror-venv/ tipped the repo
to 2.09% and blocked this commit, which adds no duplication. That directory is
the same vendored third-party shell the ignore list already excludes as
.venv/, just under another name; jscpd walks the working tree rather than the
index, so being untracked did not spare it. Excluded by name: 1.74%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 20:57:51 +02:00

390 lines
14 KiB
TOML

[project]
name = "testsandmisc"
version = "0.1.0"
description = "Collection of miscellaneous tests and scripts"
requires-python = ">=3.10"
# ============================================================================
# RUFF - Extremely fast Python linter and formatter (written in Rust)
# ============================================================================
[tool.ruff]
target-version = "py310"
# Include all Python files
include = ["*.py", "**/*.py"]
# Exclude vendored/build directories
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
".eggs",
"Bash/ffmpeg-build", # Vendored FFmpeg tools
]
[tool.ruff.lint]
# AGGRESSIVE: Select ALL rules from all categories
select = ["ALL"]
# Ignores for rules that are too strict for this mixed script repository
ignore = [
# D203 vs D211 conflict - we use D211 (no blank line before class docstring)
"D203", # 1 blank line required before class docstring (conflicts with D211)
# D212 vs D213 conflict - we use D212 (summary on first line after """)
"D213", # Multi-line docstring summary should start at second line (conflicts with D212)
# Formatter conflicts - recommended to disable when using ruff format
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"COM812", # Trailing comma missing - formatter handles this automatically
"ISC001", # Implicit string concatenation - formatter may create these when wrapping
# Security audit - prone to false positives with validated input
# https://github.com/astral-sh/ruff/issues/4045
"S603", # subprocess call without shell - prone to false positives as it is
# difficult to determine whether the passed arguments have been validated
]
# Allow ALL rules to be auto-fixed
fixable = ["ALL"]
unfixable = []
# Per-file ignores — only rules that FUNDAMENTALLY conflict with test code remain.
# Every other rule was fixed in source. See justifications below.
[tool.ruff.lint.per-file-ignores]
"**/code_tutor/cli.py" = [
"B008", # typer.Argument(...) as a parameter default is the standard Typer
# dependency-injection pattern, not a mutable-default bug.
]
"**/tests/**/*.py" = [
"ARG", # @patch decorators inject mock params that aren't always referenced;
# the patch side-effect is needed, not the mock object itself.
"D", # Test names like test_sub_cards_no_answer_text are self-documenting;
# docstrings would be redundant noise on every test method.
"PLC0415", # Test isolation requires importing AFTER mocking sys.modules;
# top-level imports would bypass the mocks entirely.
"PLR2004", # assert count == 5 is clearer than assert count == EXPECTED_COUNT;
# named constants for test expectations add indirection without value.
"S101", # assert IS what tests do — every Python test suite suppresses this.
"SLF001", # Unit tests must exercise private internals (_method, _attr) to reach
# 100% branch coverage; only integration tests can avoid this.
"TC002", # pytest resolves fixture parameter annotations at RUNTIME, so moving
"TC003", # pytest/Path into a TYPE_CHECKING block breaks collection. Verified
# empirically: guarding them failed 11 tests with UnboundLocalError
# and NameError, not a hypothetical concern.
]
"**/test_*.py" = [
"ARG",
"D",
"PLC0415",
"PLR2004",
"S101",
"SLF001",
]
[tool.ruff.lint.pylint]
max-args = 6 # code_tutor challenge flows genuinely need 6 params
max-returns = 8 # challenge state machines have multiple exit paths
[tool.ruff.lint.pydocstyle]
convention = "google" # Use Google docstring convention
[tool.ruff.lint.isort]
force-single-line = false
force-sort-within-sections = true
known-first-party = ["python_pkg"]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true
# ============================================================================
# MYPY - Static type checker (most aggressive settings)
# ============================================================================
[tool.mypy]
python_version = "3.10"
# Strict mode enables most checks
strict = true
# Additional aggressive settings
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
# Extra strict settings
disallow_any_unimported = true
disallow_any_explicit = false # Too aggressive for practical use
disallow_any_generics = true
disallow_subclassing_any = true
strict_equality = true
extra_checks = true
# Allow missing imports for third-party packages
ignore_missing_imports = true
# Show error codes
show_error_codes = true
# Enable colored output
color_output = true
# Exclude vendored directories
exclude = [
"Bash/ffmpeg-build/",
".venv/",
"linux_configuration/misc/tools/", # Avoid duplicate module named 'tools'
]
# Standalone tool scripts outside python_pkg/ are not held to strict mypy rules
[[tool.mypy.overrides]]
module = "linux_configuration.*"
ignore_errors = true
# External libraries without bundled stubs - suppress import-untyped errors
[[tool.mypy.overrides]]
module = ["requests.*", "rich.*", "anthropic.*", "typer.*"]
ignore_missing_imports = true
# code_tutor uses TYPE_CHECKING-only imports for rich/typer; types propagate as
# Any at call sites because those libs lack complete stubs in this env.
# untyped-decorator fires for @app.command() which is a Typer pattern.
[[tool.mypy.overrides]]
module = "python_pkg.code_tutor.*"
disallow_any_unimported = false
disallow_untyped_decorators = false
# ============================================================================
# PYLINT - Comprehensive Python linter
# ============================================================================
[tool.pylint.main]
# Make standalone script dirs importable so tests that import their modules by
# bare name (the same dirs linux_configuration/tests/conftest.py adds to
# sys.path at runtime) resolve under static analysis instead of raising E0401.
# Paths are relative to the repo root, which is pre-commit's working directory.
init-hook = "import sys; sys.path[:0] = ['meta/scripts', 'python_pkg', 'linux_configuration/utils', 'linux_configuration/misc/tools']"
# Analyse import fallback blocks
analyse-fallback-blocks = true
# Pickle collected data for later comparisons
persistent = true
# Jobs to use for parallel execution (0 = auto)
jobs = 0
# Minimum Python version
py-version = "3.10"
# Ignore vendored directories. "tests" and "conftest.py" are basename
# matches: test suites and pytest fixtures intentionally use patterns
# (protected-access, missing docstrings, fixture-arg shadowing) that don't
# apply to source code, so they are linted separately (not by this hook).
ignore = ["Bash", ".venv", "__pycache__", "tests", "conftest.py"]
# Ignore patterns
ignore-patterns = [".*\\.pyi$"]
# Allow C extension modules to be introspected
extension-pkg-allow-list = ["cv2", "pygame", "lxml"]
[tool.pylint.messages_control]
# Enable all checks by disabling disable
enable = "all"
# Globally disabled checks. Each is either a stylistic preference that conflicts
# with deliberate, clearer code, or a structural false positive that cannot be
# rewritten without harming readability. Everything else stays at max strictness.
disable = [
# use-implicit-booleaness family (C1803/C1804/C1805): pylint wants
# `not x` / `not s` instead of `x == 0` / `s == ""`. Explicit comparisons
# against 0 and "" state numeric/string intent more clearly than truthiness
# (and are not equivalent when the value may be None), so we keep them.
"use-implicit-booleaness-not-comparison",
"use-implicit-booleaness-not-comparison-to-string",
"use-implicit-booleaness-not-comparison-to-zero",
# consider-using-with (R1732): several subprocess.Popen calls are
# intentionally fire-and-forget — the process must outlive the calling
# scope and is polled/killed later, so a `with` block is wrong here.
"consider-using-with",
# unnecessary-ellipsis (W2301): Protocol abstract method bodies use `...`
# by Python convention (PEP 544); `pass` is equivalent but less idiomatic.
"unnecessary-ellipsis",
]
[tool.pylint.design]
# Mixins and single-entry-point classes may have zero public methods
min-public-methods = 0
# Test modules can be large
max-module-lines = 1000
# UI/mixin classes accumulate attributes across multiple mixins
max-attributes = 12
# code_tutor challenge flows genuinely need 6 params (matches ruff max-args)
max-args = 6
max-positional-arguments = 6
# Complex lesson/challenge functions need up to 20 locals
max-locals = 20
[tool.pylint.basic]
# Allow standard snake_case methods AND AST NodeVisitor visit_CapWords methods
method-rgx = "[a-z_][a-z0-9_]{2,}$|visit_[A-Za-z][A-Za-z0-9_]*"
[tool.pylint.similarities]
# 6 lines: CodeItem and ItemData share id/file/type/name/start_line/end_line fields
# intentionally (mirror the same domain schema). Raise the threshold so pylint
# doesn't flag this intentional structural duplication.
min-similarity-lines = 10
[tool.pylint.spelling]
# No spelling dictionary to avoid false positives
spelling-dict = ""
[tool.pylint.typecheck]
# cv2 (OpenCV) dynamically loads members from C extension at runtime.
# unittest.mock.MagicMock generates assertion/introspection methods at runtime.
# wave.open(path, "wb") returns a Wave_write, but pylint's stdlib stub infers the
# read-mode Wave_read overload and wrongly reports its setter/writer methods as
# missing — list them so the write API is recognised.
generated-members = [
"cv2.*",
".*\\.assert_called_once_with",
".*\\.assert_called_once",
".*\\.assert_called",
".*\\.assert_not_called",
".*\\.assert_any_call",
".*\\.call_args",
".*\\.call_args_list",
".*\\.call_count",
".*\\.setnchannels",
".*\\.setsampwidth",
".*\\.setframerate",
".*\\.writeframes",
".*\\.writeframesraw",
]
# ============================================================================
# BANDIT - Security linter
# ============================================================================
[tool.bandit]
# Exclude test directories and vendored code
exclude_dirs = ["tests", ".venv", "Bash/ffmpeg-build"]
# ============================================================================
# BLACK & ISORT - Removed (ruff handles formatting and import sorting)
# ============================================================================
# ============================================================================
# PYTEST - Testing framework configuration
# ============================================================================
[tool.pytest.ini_options]
# linux_configuration/tests covers the standalone usage_report scripts. It adds
# no python_pkg coverage (those tests don't import python_pkg), so running it
# alongside leaves the 100%-on-python_pkg gate untouched.
testpaths = ["python_pkg", "linux_configuration/tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--strict-config",
"-ra",
"--cov=python_pkg",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=lcov",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"default::pytest.PytestUnraisableExceptionWarning",
]
# ============================================================================
# COVERAGE - Code coverage configuration
# ============================================================================
[tool.coverage.run]
source = ["python_pkg"]
branch = true
omit = [
"*/__pycache__/*",
"*/tests/*",
"*/.venv/*",
]
[tool.coverage.report]
# Fail under this percentage
fail_under = 100
show_missing = true
skip_covered = false
exclude_lines = [
# Standard exclusions
"pragma: no cover",
# Unreachable defensive code
"raise NotImplementedError",
"raise AssertionError",
# Type checking imports
"if TYPE_CHECKING:",
# Main script entry point
'if __name__ == "__main__":',
]
# Partial branch exclusions for unreachable branches
partial_branches = [
"pragma: no branch",
]
# ============================================================================
# VULTURE - Dead code detection
# ============================================================================
# Note: Vulture uses command-line args, but we can document settings here
# vulture --min-confidence 80 --exclude ".venv,Bash" .
# ============================================================================
# FLAKE8 - Python linter (via Flake8-pyproject for pyproject.toml support)
# ============================================================================
[tool.flake8]
# Maximum line length (matches ruff/black)
max-line-length = 88
# Maximum McCabe complexity (matches ruff C901 threshold)
max-complexity = 10
# Maximum cognitive complexity (flake8-cognitive-complexity)
max-cognitive-complexity = 12
# Maximum function length (flake8-functions)
max-function-length = 20
# Maximum returns/arguments per function
max-returns-amount = 6
max-arguments = 5
# Docstring convention (matches ruff)
docstring-convention = "google"
# Select all error codes
select = ["E", "F", "W", "C", "B", "B950"]
# Extend with plugin codes
extend-select = ["B", "B9", "C4", "SIM", "PT", "TC", "ANN"]
# Ignore rules that conflict with ruff-format or are duplicated
extend-ignore = [
"E501", # Line too long - B950 from bugbear is smarter (allows 10% overflow)
"W503", # Line break before binary operator - contradicts PEP 8 update
"ANN101", # Missing type annotation for self
"ANN102", # Missing type annotation for cls
]
# Exclude directories
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
".eggs",
"Bash/ffmpeg-build",
]
# Per-file ignores
per-file-ignores = [
"**/tests/**/*.py:S101,ANN",
"**/test_*.py:S101,ANN",
]
# ============================================================================
# PYDOCSTYLE - Docstring style checker (ruff handles this, but for standalone)
# ============================================================================
# Configured in ruff.lint.pydocstyle above