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
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,38 @@ fabric.properties


creak_captcha_3d/src/data/

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

# Claude settings
.claude/*

# Poetry
poetry.lock.backup
dist/

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

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
2,329 changes: 2,329 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[tool.poetry]
name = "crack-captcha"
version = "0.1.0"
description = "A Python project with comprehensive testing infrastructure"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [
{ include = "crack_captcha" },
{ include = "crack_captcha_3d" },
{ include = "crack_captcha_rgb" },
{ include = "mnist" }
]

[tool.poetry.dependencies]
python = "^3.8"
Pillow = "^10.0.0"
tensorflow = "^2.13.0"
captcha = "^0.5.0"

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

[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 = [
"--strict-markers",
"--tb=short",
"--cov=crack_captcha",
"--cov=crack_captcha_3d",
"--cov=crack_captcha_rgb",
"--cov=mnist",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
"-v"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests"
]

[tool.coverage.run]
source = ["crack_captcha", "crack_captcha_3d", "crack_captcha_rgb", "mnist"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/test_*.py",
"*_test.py",
"*/conftest.py",
"*/setup.py"
]

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

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

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

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
189 changes: 189 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"""
Shared pytest fixtures and configuration for all tests.
"""
import os
import sys
import tempfile
import shutil
from pathlib import Path
from typing import Generator, Dict, Any
import pytest
from unittest.mock import MagicMock


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


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""
Provides a temporary directory that is automatically cleaned up after the test.

Yields:
Path: Path to the temporary directory
"""
temp_path = Path(tempfile.mkdtemp())
try:
yield temp_path
finally:
if temp_path.exists():
shutil.rmtree(temp_path)


@pytest.fixture
def temp_file(temp_dir: Path) -> Generator[Path, None, None]:
"""
Creates a temporary file in a temporary directory.

Args:
temp_dir: Temporary directory fixture

Yields:
Path: Path to the temporary file
"""
temp_file_path = temp_dir / "test_file.txt"
temp_file_path.write_text("test content")
yield temp_file_path


@pytest.fixture
def mock_config() -> Dict[str, Any]:
"""
Provides a mock configuration dictionary for testing.

Returns:
Dict containing mock configuration values
"""
return {
"debug": True,
"batch_size": 32,
"learning_rate": 0.001,
"epochs": 10,
"model_path": "/tmp/test_model.h5",
"data_path": "/tmp/test_data",
"image_width": 160,
"image_height": 60,
"captcha_length": 4,
"charset": "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
}


@pytest.fixture
def mock_model():
"""
Provides a mock model object for testing.

Returns:
MagicMock object configured as a model
"""
mock = MagicMock()
mock.predict.return_value = [[0.9, 0.1], [0.2, 0.8]]
mock.evaluate.return_value = [0.1, 0.95] # loss, accuracy
mock.save.return_value = None
mock.load_weights.return_value = None
return mock


@pytest.fixture
def sample_image_data():
"""
Provides sample image data for testing.

Returns:
Tuple of (image_array, label)
"""
import numpy as np

# Create a simple 60x160 RGB image (common captcha size)
image = np.random.randint(0, 255, (60, 160, 3), dtype=np.uint8)
label = "TEST"

return image, label


@pytest.fixture
def mock_tensorflow_session():
"""
Provides a mock TensorFlow session for testing.

Returns:
MagicMock object configured as a TensorFlow session
"""
session_mock = MagicMock()
session_mock.run.return_value = [0.1, 0.9]
session_mock.__enter__ = MagicMock(return_value=session_mock)
session_mock.__exit__ = MagicMock(return_value=None)
return session_mock


@pytest.fixture
def capture_stdout(monkeypatch):
"""
Captures stdout output during tests.

Returns:
List that will contain captured output lines
"""
captured = []

def mock_print(*args, **kwargs):
captured.append(" ".join(str(arg) for arg in args))

monkeypatch.setattr("builtins.print", mock_print)
return captured


@pytest.fixture(autouse=True)
def reset_modules():
"""
Automatically reset certain modules before each test to ensure isolation.
"""
modules_to_reset = [
'crack_captcha',
'crack_captcha_3d',
'crack_captcha_rgb',
'mnist'
]

for module in modules_to_reset:
if module in sys.modules:
del sys.modules[module]


@pytest.fixture
def mock_pillow_image():
"""
Provides a mock PIL Image object for testing.

Returns:
MagicMock object configured as a PIL Image
"""
mock_image = MagicMock()
mock_image.size = (160, 60)
mock_image.mode = "RGB"
mock_image.save.return_value = None
mock_image.convert.return_value = mock_image
return mock_image


@pytest.fixture
def test_data_paths(temp_dir: Path) -> Dict[str, Path]:
"""
Creates a structure of test data directories.

Args:
temp_dir: Temporary directory fixture

Returns:
Dictionary with paths to train, test, and validate directories
"""
paths = {
"train": temp_dir / "train",
"test": temp_dir / "test",
"validate": temp_dir / "validate"
}

for path in paths.values():
path.mkdir(parents=True, exist_ok=True)

return paths
Empty file added tests/integration/__init__.py
Empty file.
Loading