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
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
*.pyc

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
.tox/

# Build artifacts
build/
dist/
*.egg-info/
__pycache__/

# Virtual environments
venv/
env/
.env
.venv/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Claude settings
.claude/*

# OS files
.DS_Store
Thumbs.db
218 changes: 218 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[tool.poetry]
name = "shiftpi"
version = "0.2"
description = "ShiftPi is the easiest way to work with 74HC595 shift registers on your Raspberry Pi in Arduino style :). If you are an Arduino fan ... you'l love it :)"
authors = ["Marian Ignev <m@ignev.net>"]
readme = "README.md"
homepage = "http://m.ignev.net/code/shiftpi"
packages = [{include = "shiftpi.py"}]
classifiers = [
"Operating System :: POSIX",
"Operating System :: POSIX :: BSD",
"Operating System :: POSIX :: Linux",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Topic :: System :: Shells",
"Topic :: Utilities",
]

[tool.poetry.dependencies]
python = "^3.11"
# RPi.GPIO = "*" # Uncommented from original setup.py

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

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

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=shiftpi",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=80",
]
markers = [
"unit: marks tests as unit tests (fast isolated tests)",
"integration: marks tests as integration tests (slower tests with dependencies)",
"slow: marks tests as slow running tests",
]

[tool.coverage.run]
source = ["shiftpi"]
omit = [
"tests/*",
"setup.py",
"*/__init__.py",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
show_missing = true
precision = 2

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

[tool.coverage.xml]
output = "coverage.xml"
2 changes: 1 addition & 1 deletion shiftpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def startupMode(mode, execute = False):
else:
raise ValueError("The mode can be only HIGH or LOW or Dictionary with specific pins and modes")
elif isinstance(mode, dict):
for pin, mode in mode.iteritems():
for pin, mode in mode.items():
_setPin(pin, mode)
if execute:
_execute()
Expand Down
Empty file added tests/__init__.py
Empty file.
101 changes: 101 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Shared pytest fixtures for the shiftpi testing suite.
"""

import pytest
import tempfile
import os
import shutil
import sys
from unittest.mock import Mock, patch, MagicMock

# Mock RPi.GPIO at module level to allow imports
mock_gpio = MagicMock()
mock_gpio.BCM = 'BCM'
mock_gpio.OUT = 'OUT'
mock_gpio.HIGH = 1
mock_gpio.LOW = 0
mock_gpio.setmode = Mock()
mock_gpio.setwarnings = Mock()
mock_gpio.setup = Mock()
mock_gpio.output = Mock()

sys.modules['RPi'] = MagicMock()
sys.modules['RPi.GPIO'] = mock_gpio


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


@pytest.fixture
def mock_gpio():
"""Mock RPi.GPIO module for testing without hardware."""
with patch('shiftpi.GPIO') as mock_gpio_module:
mock_gpio_module.BCM = 'BCM'
mock_gpio_module.OUT = 'OUT'
mock_gpio_module.HIGH = 1
mock_gpio_module.LOW = 0

mock_gpio_module.setmode = Mock()
mock_gpio_module.setwarnings = Mock()
mock_gpio_module.setup = Mock()
mock_gpio_module.output = Mock()

yield mock_gpio_module


@pytest.fixture
def mock_config():
"""Provide a mock configuration for testing."""
return {
'ser_pin': 25,
'rclk_pin': 24,
'srclk_pin': 23,
'num_registers': 1
}


@pytest.fixture
def sample_pin_states():
"""Provide sample pin states for testing."""
return [0, 1, 0, 1, 1, 0, 1, 0] # 8 pins worth of test data


@pytest.fixture(autouse=True)
def reset_shiftpi_globals():
"""Reset shiftpi global variables before each test."""
import shiftpi

# Reset global variables to their initial state
shiftpi._SER_pin = 25
shiftpi._RCLK_pin = 24
shiftpi._SRCLK_pin = 23
shiftpi._registers = list()
shiftpi._number_of_shiftregisters = 1

yield

# Clean up after test
shiftpi._registers = list()


@pytest.fixture
def temp_file():
"""Create a temporary file for tests."""
fd, temp_path = tempfile.mkstemp()
os.close(fd)
yield temp_path
if os.path.exists(temp_path):
os.remove(temp_path)


@pytest.fixture
def mock_sleep():
"""Mock time.sleep for faster tests."""
with patch('shiftpi.sleep') as mock_sleep_func:
yield mock_sleep_func
Empty file added tests/integration/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Validation tests to ensure the testing infrastructure is properly set up.
"""

import pytest
import os
import sys


def test_pytest_is_working():
"""Basic test to ensure pytest is running correctly."""
assert True


def test_project_structure():
"""Test that the project structure is as expected."""
# Check that main module exists
assert os.path.exists('shiftpi.py')

# Check that testing directories exist
assert os.path.isdir('tests')
assert os.path.isdir('tests/unit')
assert os.path.isdir('tests/integration')


def test_shiftpi_import():
"""Test that the main shiftpi module can be imported."""
try:
import shiftpi
assert hasattr(shiftpi, 'version')
assert shiftpi.version == "0.2"
except ImportError:
pytest.fail("Could not import shiftpi module")


def test_fixtures_available(temp_dir, mock_config):
"""Test that shared fixtures from conftest.py are available."""
# Test temp_dir fixture
assert os.path.isdir(temp_dir)

# Test mock_config fixture
assert isinstance(mock_config, dict)
assert 'ser_pin' in mock_config


def test_mock_gpio_fixture(mock_gpio):
"""Test that GPIO mocking fixture works."""
assert mock_gpio is not None
assert hasattr(mock_gpio, 'setup')
assert hasattr(mock_gpio, 'output')


@pytest.mark.unit
def test_unit_marker():
"""Test that custom markers work."""
assert True


@pytest.mark.integration
def test_integration_marker():
"""Test that custom markers work."""
assert True


@pytest.mark.slow
def test_slow_marker():
"""Test that custom markers work."""
assert True


def test_coverage_measurement():
"""Test that this test file is being measured for coverage."""
# This test itself helps ensure coverage measurement is working
# by providing a test that will show up in coverage reports
covered_line = True
assert covered_line
Empty file added tests/unit/__init__.py
Empty file.