Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
# Build artifacts
build/
dist/
venv-*/
*.egg-info/
*.eggs/
.eggs/

# Virtual environments
venv-*/
venv/
env/
.env

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
*.cover
.hypothesis/
.tox/

# Claude
.claude/*

# IDE
.*.swp
.vscode/
.idea/
*.sublime-*

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python

# OS
.DS_Store
Thumbs.db
*~

# Package managers
pip-log.txt
pip-delete-this-directory.txt

# Documentation
docs/_build/
site/

# Jupyter
.ipynb_checkpoints/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json
283 changes: 283 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
[tool.poetry]
name = "Jetson.GPIO"
version = "2.1.9"
description = "A module to control Jetson GPIO channels"
authors = ["NVIDIA <linux-tegra-bugs@nvidia.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/NVIDIA/jetson-gpio"
keywords = ["Jetson", "GPIO"]
classifiers = [
"Operating System :: POSIX :: Linux",
"License :: OSI Approved :: MIT License",
"Intended Audience :: Developers",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Topic :: Software Development",
"Topic :: System :: Hardware"
]
packages = [
{ include = "Jetson", from = "lib/python" },
{ include = "RPi", from = "lib/python" }
]
include = ["lib/python/Jetson/GPIO/99-gpio.rules"]

[tool.poetry.dependencies]
python = ">=3.7"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*", "*Tests"]
python_functions = ["test_*"]
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=lib/python/Jetson",
"--cov=lib/python/RPi",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=0", # Set to 80 when actual tests are added
"-vv"
]
markers = [
"unit: marks tests as unit tests (fast, isolated)",
"integration: marks tests as integration tests (slower, may require hardware)",
"slow: marks tests as slow running"
]

[tool.coverage.run]
source = ["lib/python/Jetson", "lib/python/RPi"]
branch = true
omit = [
"*/tests/*",
"*/test_*.py",
"*/__init__.py",
"*/samples/*"
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod"
]
precision = 2
show_missing = true
skip_covered = false
fail_under = 0 # Set to 80 when actual tests are added

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
95 changes: 95 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import os
import sys
import tempfile
import shutil
from pathlib import Path
import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'lib', 'python'))


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_dir = tempfile.mkdtemp()
yield Path(temp_dir)
shutil.rmtree(temp_dir)


@pytest.fixture
def mock_gpio_hardware(mocker):
"""Mock the GPIO hardware interface for testing without actual hardware."""
mock_hw = mocker.MagicMock()
mock_hw.is_jetson = True
mock_hw.get_model = mocker.MagicMock(return_value="JETSON_NANO")
mock_hw.get_jetson_board_revision = mocker.MagicMock(return_value="A02")
return mock_hw


@pytest.fixture
def mock_gpio_permissions(mocker, temp_dir):
"""Mock GPIO permissions and sysfs paths."""
gpio_path = temp_dir / "sys" / "class" / "gpio"
gpio_path.mkdir(parents=True, exist_ok=True)

mocker.patch.dict(os.environ, {
"JETSON_GPIO_TEST": "1",
"JETSON_GPIO_SYSFS": str(gpio_path)
})

return gpio_path


@pytest.fixture
def gpio_cleanup():
"""Ensure GPIO cleanup after each test."""
yield
try:
import Jetson.GPIO as GPIO
GPIO.cleanup()
except Exception:
pass


@pytest.fixture
def mock_config():
"""Provide a mock configuration dictionary."""
return {
"debug": False,
"warnings": True,
"mode": "BOARD",
"pwm_frequency": 2000,
"cleanup_on_exit": True
}


@pytest.fixture
def sample_pin_data():
"""Provide sample GPIO pin data for testing."""
return {
"board_pins": {
7: {"name": "GPIO_PZ0", "gpio": 216, "pwm": None},
11: {"name": "GPIO_PQ5", "gpio": 133, "pwm": None},
12: {"name": "GPIO_PE6", "gpio": 38, "pwm": 2},
13: {"name": "GPIO_PQ6", "gpio": 134, "pwm": None},
15: {"name": "GPIO_PH1", "gpio": 57, "pwm": None}
},
"bcm_pins": {
4: {"name": "GPIO_PZ0", "gpio": 216, "pwm": None},
17: {"name": "GPIO_PQ5", "gpio": 133, "pwm": None},
18: {"name": "GPIO_PE6", "gpio": 38, "pwm": 2},
27: {"name": "GPIO_PQ6", "gpio": 134, "pwm": None},
22: {"name": "GPIO_PH1", "gpio": 57, "pwm": None}
}
}


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset imported modules to ensure clean state between tests."""
modules_to_remove = [
module for module in sys.modules
if module.startswith(('Jetson', 'RPi'))
]
for module in modules_to_remove:
del sys.modules[module]
Empty file added tests/integration/__init__.py
Empty file.
Empty file added tests/unit/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions tests/unit/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import pytest
import sys
import os


class TestSetupValidation:
"""Validation tests to ensure the testing infrastructure is properly configured."""

def test_python_path_configured(self):
"""Test that the Python path includes the library directory."""
lib_path = os.path.join(os.path.dirname(__file__), '..', '..', 'lib', 'python')
lib_path = os.path.abspath(lib_path)
assert any(os.path.abspath(path) == lib_path for path in sys.path), \
"Library path not found in sys.path"

def test_fixtures_available(self, temp_dir, mock_config, sample_pin_data):
"""Test that all required fixtures are available and working."""
assert temp_dir.exists()
assert temp_dir.is_dir()

assert isinstance(mock_config, dict)
assert "debug" in mock_config
assert "mode" in mock_config

assert isinstance(sample_pin_data, dict)
assert "board_pins" in sample_pin_data
assert "bcm_pins" in sample_pin_data

def test_mock_gpio_fixtures(self, mock_gpio_hardware, mock_gpio_permissions):
"""Test GPIO-specific fixtures."""
assert mock_gpio_hardware.is_jetson is True
assert mock_gpio_hardware.get_model() == "JETSON_NANO"
assert mock_gpio_hardware.get_jetson_board_revision() == "A02"

assert mock_gpio_permissions.exists()
assert mock_gpio_permissions.is_dir()

@pytest.mark.unit
def test_unit_marker(self):
"""Test that the unit marker is properly configured."""
assert True

@pytest.mark.integration
def test_integration_marker(self):
"""Test that the integration marker is properly configured."""
assert True

@pytest.mark.slow
def test_slow_marker(self):
"""Test that the slow marker is properly configured."""
assert True

def test_coverage_configured(self):
"""Test that coverage is properly configured."""
try:
import coverage
assert True
except ImportError:
pytest.fail("Coverage module not installed")

def test_pytest_mock_available(self):
"""Test that pytest-mock is available."""
try:
import pytest_mock
assert True
except ImportError:
pytest.fail("pytest-mock module not installed")