Skip to content

Commit fbabbd3

Browse files
committed
feat: Set up comprehensive Python testing infrastructure with Poetry
- Configure Poetry as package manager with migrated dependencies - Add pytest, pytest-cov, and pytest-mock as dev dependencies - Configure pytest with coverage thresholds and custom markers - Create testing directory structure with shared fixtures - Update .gitignore with testing and build artifacts
1 parent c04f9de commit fbabbd3

8 files changed

Lines changed: 1341 additions & 1 deletion

File tree

.gitignore

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,38 @@
11
.idea
22
venv
33
chapter01/evil
4-
chapter02/ssh_brutekey_keys
4+
chapter02/ssh_brutekey_keys
5+
6+
# Testing related
7+
.pytest_cache/
8+
.coverage
9+
htmlcov/
10+
coverage.xml
11+
*.py[cod]
12+
*$py.class
13+
14+
# Claude settings
15+
.claude/*
16+
17+
# Virtual environments
18+
.venv/
19+
env/
20+
ENV/
21+
22+
# Build artifacts
23+
build/
24+
dist/
25+
*.egg-info/
26+
.eggs/
27+
28+
# IDE files
29+
.vscode/
30+
*.swp
31+
*.swo
32+
*~
33+
.DS_Store
34+
35+
# Cache files
36+
__pycache__/
37+
*.pyc
38+
*.pyo

poetry.lock

Lines changed: 932 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
[tool.poetry]
2+
name = "violent-python3"
3+
version = "0.1.0"
4+
description = "Python 3 source code for the book 'Violent Python' by TJ O'Connor"
5+
authors = ["Your Name <you@example.com>"]
6+
readme = "README.md"
7+
packages = [{include = "chapter01"}, {include = "chapter02"}, {include = "chapter03"}, {include = "chapter04"}, {include = "chapter05"}, {include = "chapter06"}, {include = "chapter07"}]
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.8"
11+
pexpect = "~4.8.0"
12+
ptyprocess = "~0.6.0"
13+
python-nmap = "^0.7.1"
14+
beautifulsoup4 = "~4.9.1"
15+
Pillow = ">=8.3.2"
16+
pypdf4 = "~1.27.0"
17+
requests = "~2.23.0"
18+
geoip2 = "~3.0.0"
19+
dpkt = "~1.9.2"
20+
scapy = "~2.4.3"
21+
IPy = "~1.0"
22+
pybluez = {version = "~0.23", optional = true, markers = "sys_platform == 'darwin'"}
23+
MechanicalSoup = "~0.12.0"
24+
25+
[tool.poetry.group.dev.dependencies]
26+
pytest = "^7.4.0"
27+
pytest-cov = "^4.1.0"
28+
pytest-mock = "^3.11.1"
29+
30+
[tool.pytest.ini_options]
31+
minversion = "7.0"
32+
testpaths = ["tests"]
33+
python_files = "test_*.py"
34+
python_classes = "Test*"
35+
python_functions = "test_*"
36+
addopts = [
37+
"-ra",
38+
"--strict-markers",
39+
"--cov=chapter01",
40+
"--cov=chapter02",
41+
"--cov=chapter03",
42+
"--cov=chapter04",
43+
"--cov=chapter05",
44+
"--cov=chapter06",
45+
"--cov=chapter07",
46+
"--cov-branch",
47+
"--cov-report=term-missing:skip-covered",
48+
"--cov-report=html",
49+
"--cov-report=xml",
50+
"--cov-fail-under=80",
51+
]
52+
markers = [
53+
"unit: marks tests as unit tests (fast)",
54+
"integration: marks tests as integration tests (slower)",
55+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
56+
]
57+
58+
[tool.coverage.run]
59+
source = ["chapter01", "chapter02", "chapter03", "chapter04", "chapter05", "chapter06", "chapter07"]
60+
branch = true
61+
omit = [
62+
"*/tests/*",
63+
"*/__pycache__/*",
64+
"*/venv/*",
65+
"*/.venv/*",
66+
]
67+
68+
[tool.coverage.report]
69+
precision = 2
70+
show_missing = true
71+
skip_covered = true
72+
fail_under = 80
73+
exclude_lines = [
74+
"pragma: no cover",
75+
"def __repr__",
76+
"if __name__ == .__main__.:",
77+
"raise AssertionError",
78+
"raise NotImplementedError",
79+
"if TYPE_CHECKING:",
80+
]
81+
82+
[tool.coverage.html]
83+
directory = "htmlcov"
84+
85+
[tool.coverage.xml]
86+
output = "coverage.xml"
87+
88+
# Scripts can be run with: poetry run python -m pytest
89+
# Or directly with: .venv/bin/pytest
90+
91+
[build-system]
92+
requires = ["poetry-core"]
93+
build-backend = "poetry.core.masonry.api"

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test package initialization

tests/conftest.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""Shared pytest fixtures for the test suite."""
2+
import os
3+
import tempfile
4+
import shutil
5+
from pathlib import Path
6+
from unittest.mock import Mock, MagicMock
7+
import pytest
8+
9+
10+
@pytest.fixture
11+
def temp_dir():
12+
"""Create a temporary directory for test files."""
13+
temp_path = tempfile.mkdtemp()
14+
yield Path(temp_path)
15+
# Cleanup after test
16+
shutil.rmtree(temp_path, ignore_errors=True)
17+
18+
19+
@pytest.fixture
20+
def temp_file(temp_dir):
21+
"""Create a temporary file in the temp directory."""
22+
def _make_temp_file(name="test_file.txt", content=""):
23+
file_path = temp_dir / name
24+
file_path.write_text(content)
25+
return file_path
26+
return _make_temp_file
27+
28+
29+
@pytest.fixture
30+
def mock_config():
31+
"""Create a mock configuration object."""
32+
config = Mock()
33+
config.debug = False
34+
config.verbose = True
35+
config.timeout = 30
36+
config.max_retries = 3
37+
return config
38+
39+
40+
@pytest.fixture
41+
def mock_network_response():
42+
"""Create a mock network response."""
43+
response = Mock()
44+
response.status_code = 200
45+
response.text = "Mock response content"
46+
response.json.return_value = {"status": "success", "data": []}
47+
response.headers = {"Content-Type": "application/json"}
48+
return response
49+
50+
51+
@pytest.fixture
52+
def mock_socket():
53+
"""Create a mock socket object."""
54+
sock = MagicMock()
55+
sock.connect.return_value = None
56+
sock.send.return_value = 10
57+
sock.recv.return_value = b"Mock data"
58+
sock.close.return_value = None
59+
return sock
60+
61+
62+
@pytest.fixture
63+
def sample_pcap_data():
64+
"""Provide sample PCAP packet data for testing."""
65+
# This would contain mock packet data
66+
# In real tests, you might load actual test PCAP files
67+
return b"\xd4\xc3\xb2\xa1\x02\x00\x04\x00" # PCAP magic number
68+
69+
70+
@pytest.fixture
71+
def sample_credentials():
72+
"""Provide sample credentials for testing."""
73+
return {
74+
"username": "test_user",
75+
"password": "test_pass",
76+
"host": "127.0.0.1",
77+
"port": 22
78+
}
79+
80+
81+
@pytest.fixture
82+
def mock_ssh_client():
83+
"""Create a mock SSH client."""
84+
client = Mock()
85+
client.connect.return_value = None
86+
client.exec_command.return_value = (
87+
Mock(), # stdin
88+
Mock(read=lambda: b"command output"), # stdout
89+
Mock(read=lambda: b"") # stderr
90+
)
91+
client.close.return_value = None
92+
return client
93+
94+
95+
@pytest.fixture
96+
def sample_pdf_content():
97+
"""Provide sample PDF content for testing."""
98+
# Minimal PDF structure
99+
return b"%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n%%EOF"
100+
101+
102+
@pytest.fixture
103+
def sample_html_content():
104+
"""Provide sample HTML content for testing."""
105+
return """
106+
<html>
107+
<head><title>Test Page</title></head>
108+
<body>
109+
<h1>Test Content</h1>
110+
<img src="test.jpg" alt="Test Image">
111+
<p>Sample paragraph</p>
112+
</body>
113+
</html>
114+
"""
115+
116+
117+
@pytest.fixture(autouse=True)
118+
def reset_environment(monkeypatch):
119+
"""Reset environment variables for each test."""
120+
# Store original environment
121+
original_env = os.environ.copy()
122+
123+
yield
124+
125+
# Restore original environment
126+
os.environ.clear()
127+
os.environ.update(original_env)
128+
129+
130+
@pytest.fixture
131+
def capture_stdout(monkeypatch):
132+
"""Capture stdout for testing print statements."""
133+
import io
134+
import sys
135+
136+
captured_output = io.StringIO()
137+
monkeypatch.setattr(sys, 'stdout', captured_output)
138+
139+
yield captured_output
140+
141+
# Reset stdout
142+
monkeypatch.undo()
143+
144+
145+
# Markers for test organization
146+
def pytest_configure(config):
147+
"""Register custom markers."""
148+
config.addinivalue_line(
149+
"markers", "unit: mark test as a unit test"
150+
)
151+
config.addinivalue_line(
152+
"markers", "integration: mark test as an integration test"
153+
)
154+
config.addinivalue_line(
155+
"markers", "slow: mark test as slow running"
156+
)
157+
config.addinivalue_line(
158+
"markers", "network: mark test as requiring network access"
159+
)
160+
config.addinivalue_line(
161+
"markers", "skipif_no_bluetooth: skip if bluetooth is not available"
162+
)

tests/integration/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Integration tests package initialization

0 commit comments

Comments
 (0)