Skip to content

Commit df89e7e

Browse files
authored
Improving testing coverage and pre-commit checks before release (#43)
* Adjust coverage pragmas and omit entries Rework test-coverage annotations and update .coveragerc. Move # pragma: no cover/cover markers inline on classes in dlc_processor_socket.py (OneEuroFilter and example processor classes) to more precisely control coverage. Comment out the explicit omit list in .coveragerc so those backend shims are no longer excluded there. No functional behavior changed; these edits only affect test coverage reporting and file trailing-newline cleanup. * Add ScrubSpinBox tests and mouse helpers Add comprehensive tests for ScrubSpinBox and ScrubDoubleSpinBox behavior, including fixtures, event helpers, and modifier handling. Introduces _send_mouse, _press, _move, and _release helpers to send QMouseEvent directly (reduces flakiness), plus spin/dspin fixtures configured for scrubbing. Tests cover initialization defaults, tooltip instruction handling, scrubbing threshold and accumulation, Shift/Ctrl modifier effects, rounding/coercion behavior for integer and double spinboxes, and ensuring coercion avoids zero step. Also updates imports and adds small test section headers for clarity. * Update pre-commit config and add hooks Bump pre-commit-hooks to v6.0.0 and add the check-toml hook; bump ruff-pre-commit to v0.15.0. Add pyproject-fmt (v2.15.2) and validate-pyproject (v0.25) repos to format and validate pyproject.toml files. These changes add TOML checks and pyproject formatting/validation and update tooling versions. * Include tests and enable branch coverage Update coverage configuration in pyproject.toml: add "tests" to run.source and enable branch coverage (branch = true); remove tests omission from the run omit list and add an omit rule under [tool.coverage.report] to exclude tests/* from generated reports. This collects branch metrics and includes tests in coverage analysis while keeping tests out of the final report output.
1 parent f94484f commit df89e7e

File tree

5 files changed

+347
-125
lines changed

5 files changed

+347
-125
lines changed

.coveragerc

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,4 @@
33
[run]
44
branch = True
55
source = dlclivegui
6-
omit =
7-
# omit only the parts that are pure passthrough shims to SDKs
8-
dlclivegui/cameras/backends/basler_backend.py
9-
dlclivegui/cameras/backends/gentl_backend.py
6+
# omit =

.pre-commit-config.yaml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.4.0
3+
rev: v6.0.0
44
hooks:
55
- id: check-added-large-files
66
- id: check-yaml
7+
- id: check-toml
78
- id: end-of-file-fixer
89
- id: name-tests-test
910
args: [--pytest-test-first]
1011
- id: trailing-whitespace
1112
- id: check-merge-conflict
13+
- repo: https://github.com/tox-dev/pyproject-fmt
14+
rev: v2.15.2
15+
hooks:
16+
- id: pyproject-fmt
17+
- repo: https://github.com/abravalheri/validate-pyproject
18+
rev: v0.25
19+
hooks:
20+
- id: validate-pyproject
1221
- repo: https://github.com/astral-sh/ruff-pre-commit
13-
rev: v0.14.10
22+
rev: v0.15.0
1423
hooks:
1524
# Run the formatter.
1625
- id: ruff-format

dlclivegui/processors/dlc_processor_socket.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ def register_processor(cls):
4040
return cls
4141

4242

43-
# pragma: no cover
44-
class OneEuroFilter:
43+
class OneEuroFilter: # pragma: no cover
4544
def __init__(self, t0, x0, dx0=None, min_cutoff=1.0, beta=0.0, d_cutoff=1.0):
4645
self.min_cutoff = min_cutoff
4746
self.beta = beta
@@ -81,8 +80,6 @@ def __call__(self, t, x):
8180

8281

8382
# pragma: cover
84-
85-
8683
class BaseProcessorSocket(Processor):
8784
"""
8885
Base processor class that implements a socket server to help remote control recording in experiments.
@@ -492,7 +489,7 @@ def get_data(self):
492489

493490

494491
@register_processor
495-
class ExampleProcessorSocketCalculateMousePose(BaseProcessorSocket):
492+
class ExampleProcessorSocketCalculateMousePose(BaseProcessorSocket): # pragma: no cover
496493
"""
497494
DLC Processor with pose calculations (center, heading, head angle) and optional filtering.
498495
@@ -663,7 +660,7 @@ def get_data(self):
663660

664661

665662
@register_processor
666-
class ExampleProcessorSocketFilterKeypoints(BaseProcessorSocket):
663+
class ExampleProcessorSocketFilterKeypoints(BaseProcessorSocket): # pragma: no cover
667664
PROCESSOR_NAME = "Mouse Pose with less keypoints"
668665
PROCESSOR_DESCRIPTION = "Calculates mouse center, heading, and head angle with optional One-Euro filtering"
669666
PROCESSOR_PARAMS = {

pyproject.toml

Lines changed: 102 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,131 @@
11
[build-system]
2-
requires = ["setuptools>=68.0"]
32
build-backend = "setuptools.build_meta"
3+
requires = [ "setuptools>=68" ]
44

55
[project]
66
name = "deeplabcut-live-gui"
7-
dynamic = ["version"]
87
description = "PySide6-based GUI to run real time DeepLabCut experiments"
98
readme = "README.md"
10-
requires-python = ">=3.10"
11-
license-files = ["LICENSE"]
12-
authors = [
13-
{name = "A. & M. Mathis Labs", email = "adim@deeplabcut.org"}
14-
]
15-
keywords = ["deeplabcut", "pose estimation", "real-time", "gui", "deep learning"]
9+
keywords = [ "deep learning", "deeplabcut", "gui", "pose estimation", "real-time" ]
10+
license-files = [ "LICENSE" ]
11+
authors = [ { name = "A. & M. Mathis Labs", email = "adim@deeplabcut.org" } ]
12+
requires-python = ">=3.10,<3.13"
1613
classifiers = [
17-
"Programming Language :: Python :: 3",
18-
"Programming Language :: Python :: 3.10",
19-
"Programming Language :: Python :: 3.11",
20-
"Programming Language :: Python :: 3.12",
21-
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
22-
"Operating System :: OS Independent",
23-
"Development Status :: 4 - Beta",
24-
"Intended Audience :: Science/Research",
25-
"Topic :: Scientific/Engineering :: Artificial Intelligence",
26-
"Framework :: Qt",
27-
"Environment :: X11 Applications :: Qt",
14+
"Development Status :: 4 - Beta",
15+
"Environment :: X11 Applications :: Qt",
16+
"Intended Audience :: Science/Research",
17+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
18+
"Operating System :: OS Independent",
19+
"Programming Language :: Python :: 3 :: Only",
20+
"Programming Language :: Python :: 3.10",
21+
"Programming Language :: Python :: 3.11",
22+
"Programming Language :: Python :: 3.12",
23+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
2824
]
29-
25+
dynamic = [ "version" ]
3026
dependencies = [
31-
"deeplabcut-live>=2.0.0", # might be missing timm and scipy
32-
"PySide6",
33-
"qdarkstyle",
34-
"numpy",
35-
"opencv-python",
36-
"cv2-enumerate-cameras",
37-
"pydantic>=2.0",
38-
"vidgear[core]",
39-
"matplotlib",
27+
"cv2-enumerate-cameras",
28+
"deeplabcut-live>=2", # might be missing timm and scipy
29+
"matplotlib",
30+
"numpy",
31+
"opencv-python",
32+
"pydantic>=2",
33+
"pyside6",
34+
"qdarkstyle",
35+
"vidgear[core]",
4036
]
41-
42-
[project.optional-dependencies]
43-
basler = ["pypylon"]
44-
gentl = ["harvesters"]
45-
all = ["pypylon", "harvesters"]
46-
pytorch = [
47-
"deeplabcut-live[pytorch]>=2.0.0", # this includes timm and scipy
37+
optional-dependencies.all = [ "harvesters", "pypylon" ]
38+
optional-dependencies.basler = [ "pypylon" ]
39+
optional-dependencies.dev = [
40+
"hypothesis>=6",
41+
"pre-commit",
42+
"pytest>=7",
43+
"pytest-cov>=4",
44+
"pytest-mock>=3.10",
45+
"pytest-qt>=4.2",
4846
]
49-
tf = [
50-
"deeplabcut-live[tf]>=2.0.0",
47+
optional-dependencies.gentl = [ "harvesters" ]
48+
optional-dependencies.pytorch = [
49+
"deeplabcut-live[pytorch]>=2", # this includes timm and scipy
5150
]
52-
dev = [
53-
"pytest>=7.0",
54-
"pytest-cov>=4.0",
55-
"pytest-mock>=3.10",
56-
"pytest-qt>=4.2",
57-
"pre-commit",
58-
"hypothesis>=6.0",
51+
optional-dependencies.test = [
52+
"hypothesis>=6",
53+
"pytest>=7",
54+
"pytest-cov>=4",
55+
"pytest-mock>=3.10",
56+
"pytest-qt>=4.2",
5957
]
60-
test = [
61-
"pytest>=7.0",
62-
"pytest-cov>=4.0",
63-
"pytest-mock>=3.10",
64-
"pytest-qt>=4.2",
65-
"hypothesis>=6.0",
58+
optional-dependencies.tf = [
59+
"deeplabcut-live[tf]>=2",
6660
]
61+
urls."Bug Tracker" = "https://github.com/DeepLabCut/DeepLabCut-live-GUI/issues"
62+
urls.Documentation = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
63+
urls.Homepage = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
64+
urls.Repository = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
65+
scripts.dlclivegui = "dlclivegui:main"
6766

68-
[project.urls]
69-
Homepage = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
70-
Repository = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
71-
Documentation = "https://github.com/DeepLabCut/DeepLabCut-live-GUI"
72-
"Bug Tracker" = "https://github.com/DeepLabCut/DeepLabCut-live-GUI/issues"
73-
74-
[project.scripts]
75-
dlclivegui = "dlclivegui:main"
76-
67+
[tool.setuptools]
7768
# [tool.setuptools]
69+
7870
# include-package-data = true
7971

8072
# This is more granular and explicit than include-package-data,
73+
8174
# which can be too broad and include unwanted files.
82-
[tool.setuptools.package-data]
83-
"dlclivegui.assets" = ["*.png"]
8475

76+
package-data."dlclivegui.assets" = [ "*.png" ]
77+
packages.find.where = [ "." ]
78+
packages.find.include = [ "dlclivegui*" ]
79+
packages.find.exclude = [ "tests*", "docs*" ]
80+
81+
[tool.ruff]
82+
target-version = "py310"
83+
line-length = 120
84+
fix = true
85+
lint.select = [ "B", "E", "F", "I", "UP" ]
86+
lint.ignore = [ "E741" ]
8587

86-
[tool.setuptools.packages.find]
87-
where = ["."]
88-
include = ["dlclivegui*"]
89-
exclude = ["tests*", "docs*"]
88+
[tool.pyproject-fmt]
89+
generate-python-classifiers = false
9090

91-
[tool.pytest.ini_options]
92-
testpaths = ["tests"]
93-
python_files = ["test_*.py"]
94-
python_classes = ["Test*"]
95-
python_functions = ["test_*"]
96-
addopts = [
97-
"--strict-markers",
98-
"--strict-config",
99-
"--disable-warnings",
100-
# "--maxfail=1",
101-
"-ra",
102-
"-q",
91+
[tool.pytest]
92+
ini_options.testpaths = [ "tests" ]
93+
ini_options.python_files = [ "test_*.py" ]
94+
ini_options.python_classes = [ "Test*" ]
95+
ini_options.python_functions = [ "test_*" ]
96+
ini_options.addopts = [
97+
"--strict-markers",
98+
"--strict-config",
99+
"--disable-warnings",
100+
# "--maxfail=1",
101+
"-ra",
102+
"-q",
103103
]
104-
markers = [
105-
"unit: Unit tests for individual components",
106-
"integration: Integration tests for component interaction",
107-
"functional: Functional tests for end-to-end workflows",
108-
"hardware: Tests that require specific hardware, notable camera backends",
109-
# "slow: Tests that take a long time to run",
110-
"gui: Tests that require GUI interaction",
104+
ini_options.markers = [
105+
"unit: Unit tests for individual components",
106+
"integration: Integration tests for component interaction",
107+
"functional: Functional tests for end-to-end workflows",
108+
"hardware: Tests that require specific hardware, notable camera backends",
109+
# "slow: Tests that take a long time to run",
110+
"gui: Tests that require GUI interaction",
111111
]
112112

113-
[tool.coverage.run]
114-
source = ["dlclivegui"]
115-
omit = [
116-
"*/tests/*",
117-
"*/__pycache__/*",
118-
"*/site-packages/*",
113+
[tool.coverage]
114+
run.branch = true
115+
run.omit = [
116+
"*/__pycache__/*",
117+
"*/site-packages/*",
119118
]
120-
121-
[tool.coverage.report]
122-
exclude_lines = [
123-
"pragma: no cover",
124-
"def __repr__",
125-
"raise AssertionError",
126-
"raise NotImplementedError",
127-
"if __name__ == .__main__.:",
128-
"if TYPE_CHECKING:",
129-
"@abstract",
119+
run.source = [ "dlclivegui", "tests" ]
120+
report.exclude_lines = [
121+
"@abstract",
122+
"def __repr__",
123+
"if __name__ == .__main__.:",
124+
"if TYPE_CHECKING:",
125+
"pragma: no cover",
126+
"raise AssertionError",
127+
"raise NotImplementedError",
128+
]
129+
report.omit = [
130+
"tests/*",
130131
]
131-
132-
[tool.ruff]
133-
lint.select = ["E", "F", "B", "I", "UP"]
134-
lint.ignore = ["E741"]
135-
target-version = "py310"
136-
fix = true
137-
line-length = 120
138-
139-
[tool.ruff.lint.pydocstyle]
140-
convention = "google"

0 commit comments

Comments
 (0)