Skip to content

feat: Set up comprehensive Python testing infrastructure with Poetry #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
79 changes: 78 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,78 @@
*.pyc
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.pyc

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Testing and coverage
.pytest_cache/
.coverage
.coverage.*
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/
.tox/
nosetests.xml
coverage.xml
*.cover

# Virtual environments
venv/
ENV/
env/
.venv/
.virtualenv/
virtualenv/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.project
.pydevproject
.settings/

# OS files
.DS_Store
Thumbs.db

# Claude settings
.claude/*

# Poetry
# Note: poetry.lock is tracked in version control

# Logs
*.log

# Local environment files
.env
.env.local
283 changes: 283 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
[tool.poetry]
name = "SimpleWebSocketServer"
version = "0.1.1"
description = "A Simple Websocket Server written in Python"
authors = ["Dave Pallot <[email protected]>"]
readme = "README.md"
repository = "https://github.com/dpallot/simple-websocket-server/"
classifiers = [
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
packages = [{include = "SimpleWebSocketServer"}]

[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*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
"--cov=SimpleWebSocketServer",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=9", # TODO: Update to 80 when actual tests are written
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["SimpleWebSocketServer"]
branch = true
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/venv/*",
"*/virtualenv/*",
"*/.venv/*",
"*/.virtualenv/*",
]

[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__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = false
skip_empty = true

[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.
122 changes: 122 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Shared pytest fixtures and configuration for all tests."""

import os
import sys
import tempfile
import shutil
from contextlib import contextmanager
from unittest.mock import Mock, patch

import pytest

# Add the project root to the Python path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield temp_path
# Cleanup after test
shutil.rmtree(temp_path, ignore_errors=True)


@pytest.fixture
def mock_websocket():
"""Create a mock websocket client for testing."""
mock_ws = Mock()
mock_ws.address = ('127.0.0.1', 12345)
mock_ws.data = None
mock_ws.send = Mock()
mock_ws.close = Mock()
return mock_ws


@pytest.fixture
def mock_server():
"""Create a mock websocket server instance."""
with patch('SimpleWebSocketServer.SimpleWebSocketServer.SimpleWebSocketServer') as mock:
server = mock.return_value
server.serveforever = Mock()
server.close = Mock()
yield server


@pytest.fixture
def sample_config():
"""Provide sample configuration for testing."""
return {
'host': '127.0.0.1',
'port': 8000,
'ssl': False,
'certfile': None,
'keyfile': None,
'version': 'tls'
}


@pytest.fixture
def mock_ssl_context():
"""Create a mock SSL context for testing HTTPS functionality."""
with patch('ssl.SSLContext') as mock_context:
context = mock_context.return_value
context.load_cert_chain = Mock()
yield context


@contextmanager
def capture_stdout():
"""Context manager to capture stdout output."""
import io
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stdout = old_stdout


@pytest.fixture
def capture_output():
"""Fixture to capture stdout output during tests."""
with capture_stdout() as buffer:
yield buffer


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset imported modules to ensure test isolation."""
# Store original modules
original_modules = sys.modules.copy()
yield
# Restore original modules
for module in list(sys.modules.keys()):
if module.startswith('SimpleWebSocketServer') and module in original_modules:
sys.modules[module] = original_modules[module]


@pytest.fixture
def sample_websocket_message():
"""Provide sample websocket messages for testing."""
return {
'text': b'Hello, WebSocket!',
'binary': b'\x00\x01\x02\x03\x04',
'ping': b'ping',
'pong': b'pong',
'close': b'\x03\xe8' # Close frame with code 1000
}


def pytest_configure(config):
"""Configure pytest with custom settings."""
# Register custom markers
config.addinivalue_line(
"markers", "unit: mark test as a unit test"
)
config.addinivalue_line(
"markers", "integration: mark test as an integration test"
)
config.addinivalue_line(
"markers", "slow: mark test as slow running"
)
Empty file added tests/integration/__init__.py
Empty file.
Loading