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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,9 @@ dmypy.json

# Pyre type checker
.pyre/

# Claude settings
.claude/*

# Poetry (keep lock file)
# poetry.lock is tracked
549 changes: 549 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[tool.poetry]
name = "ddddocr-project"
version = "0.1.0"
description = "Python OCR project with ddddocr"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [{include = "configs"}, {include = "nets"}, {include = "projects"}, {include = "tools"}, {include = "utils"}]

[tool.poetry.dependencies]
python = "^3.8"
fire = "*"
loguru = "*"
pyyaml = "*"
tqdm = "*"
numpy = "<2"
pillow = "9.5.0"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-cov = "^5.0.0"
pytest-mock = "^3.14.0"

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

[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--verbose",
"--cov=configs",
"--cov=tools",
"--cov=utils",
"--cov-report=term-missing:skip-covered",
"--cov-report=html",
"--cov-report=xml",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests"
]

[tool.coverage.run]
source = ["configs", "tools", "utils"]
omit = [
"*/tests/*",
"*/__init__.py",
"*/migrations/*",
"*/.venv/*",
"*/venv/*",
"*/nets/*", # Exclude nets module due to torch dependency
"*/projects/*", # Exclude projects module for now
]

[tool.coverage.report]
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.
130 changes: 130 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Shared pytest fixtures and configuration."""
import shutil
import tempfile
from pathlib import Path
from typing import Generator, Dict, Any

import pytest
import yaml


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test files."""
temp_path = Path(tempfile.mkdtemp())
try:
yield temp_path
finally:
shutil.rmtree(temp_path)


@pytest.fixture
def mock_config() -> Dict[str, Any]:
"""Provide a mock configuration dictionary."""
return {
"model": {
"name": "test_model",
"type": "ddddocr",
"backbone": "mobilenetv2",
"num_classes": 10,
},
"training": {
"batch_size": 32,
"learning_rate": 0.001,
"epochs": 10,
"save_interval": 1000,
},
"data": {
"train_path": "/path/to/train",
"val_path": "/path/to/val",
"test_path": "/path/to/test",
},
}


@pytest.fixture
def sample_yaml_config(temp_dir: Path, mock_config: Dict[str, Any]) -> Path:
"""Create a sample YAML configuration file."""
config_path = temp_dir / "config.yaml"
with open(config_path, "w") as f:
yaml.dump(mock_config, f)
return config_path


@pytest.fixture
def mock_image_path(temp_dir: Path) -> Path:
"""Create a mock image file path."""
image_path = temp_dir / "test_image.jpg"
# Create an empty file to represent the image
image_path.touch()
return image_path


@pytest.fixture
def mock_checkpoint_path(temp_dir: Path) -> Path:
"""Create a mock checkpoint file path."""
checkpoint_path = temp_dir / "test_checkpoint.tar"
# Create an empty file to represent the checkpoint
checkpoint_path.touch()
return checkpoint_path


@pytest.fixture
def sample_project_structure(temp_dir: Path) -> Path:
"""Create a sample project directory structure."""
# Create directories
(temp_dir / "configs").mkdir()
(temp_dir / "nets" / "backbone").mkdir(parents=True)
(temp_dir / "utils").mkdir()
(temp_dir / "tools").mkdir()

# Create __init__.py files
(temp_dir / "configs" / "__init__.py").touch()
(temp_dir / "nets" / "__init__.py").touch()
(temp_dir / "nets" / "backbone" / "__init__.py").touch()
(temp_dir / "utils" / "__init__.py").touch()
(temp_dir / "tools" / "__init__.py").touch()

return temp_dir


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset module imports between tests to avoid state pollution."""
import sys
modules_to_remove = [
module for module in sys.modules
if module.startswith(('configs', 'nets', 'utils', 'tools', 'projects'))
]
for module in modules_to_remove:
sys.modules.pop(module, None)
yield


@pytest.fixture
def capture_logs():
"""Capture loguru logs for testing."""
from loguru import logger
from io import StringIO

log_capture = StringIO()
handler_id = logger.add(
log_capture,
format="{time} | {level} | {message}",
level="DEBUG"
)

yield log_capture

logger.remove(handler_id)


@pytest.fixture
def mock_training_data() -> Dict[str, Any]:
"""Provide mock training data."""
return {
"images": ["image1.jpg", "image2.jpg", "image3.jpg"],
"labels": ["label1", "label2", "label3"],
"batch_size": 2,
"num_epochs": 5,
}
Empty file added tests/integration/__init__.py
Empty file.
158 changes: 158 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"""Validation tests to ensure the testing infrastructure is properly set up."""
import sys
from pathlib import Path

import pytest


class TestSetupValidation:
"""Tests to validate the testing infrastructure setup."""

def test_python_version(self):
"""Verify Python version is 3.8 or higher."""
assert sys.version_info >= (3, 8), "Python 3.8 or higher is required"

def test_pytest_imported(self):
"""Verify pytest can be imported."""
import pytest
assert pytest.__version__

def test_pytest_cov_imported(self):
"""Verify pytest-cov can be imported."""
import pytest_cov
assert pytest_cov.__version__

def test_pytest_mock_imported(self):
"""Verify pytest-mock can be imported."""
import pytest_mock
# pytest_mock doesn't expose version directly, check it exists
assert pytest_mock

def test_project_structure_exists(self):
"""Verify the project structure exists."""
workspace = Path(__file__).parent.parent

# Check main directories
assert (workspace / "configs").exists()
assert (workspace / "nets").exists()
assert (workspace / "utils").exists()
assert (workspace / "tools").exists()
assert (workspace / "projects").exists()

# Check test directories
assert (workspace / "tests").exists()
assert (workspace / "tests" / "unit").exists()
assert (workspace / "tests" / "integration").exists()

def test_conftest_fixtures(self, temp_dir, mock_config):
"""Verify conftest fixtures are working."""
# Test temp_dir fixture
assert temp_dir.exists()
assert temp_dir.is_dir()

# Test mock_config fixture
assert isinstance(mock_config, dict)
assert "model" in mock_config
assert "training" in mock_config
assert "data" in mock_config

@pytest.mark.unit
def test_unit_marker(self):
"""Verify unit test marker is working."""
assert True

@pytest.mark.integration
def test_integration_marker(self):
"""Verify integration test marker is working."""
assert True

@pytest.mark.slow
def test_slow_marker(self):
"""Verify slow test marker is working."""
assert True

def test_coverage_configured(self):
"""Verify coverage is properly configured."""
# This test will be run with coverage enabled
# The actual verification happens through the coverage report
assert True

def test_imports_from_project_modules(self):
"""Verify project modules can be imported."""
# Note: Some modules may have dependencies like torch that aren't installed
# This test verifies the module structure is correct, not all dependencies
try:
import configs
# Skip nets as it requires torch
import utils
import tools
import projects
assert True
except ImportError as e:
# Expected for modules with missing dependencies
if "torch" not in str(e):
pytest.fail(f"Failed to import project module: {e}")


class TestFixtureValidation:
"""Tests to validate pytest fixtures are working correctly."""

def test_sample_yaml_config_fixture(self, sample_yaml_config, mock_config):
"""Verify sample_yaml_config fixture creates a valid YAML file."""
assert sample_yaml_config.exists()
assert sample_yaml_config.suffix == ".yaml"

# Read and verify content
import yaml
with open(sample_yaml_config, "r") as f:
loaded_config = yaml.safe_load(f)

assert loaded_config == mock_config

def test_mock_image_path_fixture(self, mock_image_path):
"""Verify mock_image_path fixture creates a file."""
assert mock_image_path.exists()
assert mock_image_path.name == "test_image.jpg"

def test_mock_checkpoint_path_fixture(self, mock_checkpoint_path):
"""Verify mock_checkpoint_path fixture creates a file."""
assert mock_checkpoint_path.exists()
assert mock_checkpoint_path.name == "test_checkpoint.tar"

def test_sample_project_structure_fixture(self, sample_project_structure):
"""Verify sample_project_structure fixture creates the expected structure."""
assert (sample_project_structure / "configs" / "__init__.py").exists()
assert (sample_project_structure / "nets" / "__init__.py").exists()
assert (sample_project_structure / "nets" / "backbone" / "__init__.py").exists()
assert (sample_project_structure / "utils" / "__init__.py").exists()
assert (sample_project_structure / "tools" / "__init__.py").exists()

def test_capture_logs_fixture(self, capture_logs):
"""Verify capture_logs fixture works with loguru."""
from loguru import logger

logger.info("Test log message")
logger.warning("Test warning")

logs = capture_logs.getvalue()
assert "Test log message" in logs
assert "Test warning" in logs
assert "INFO" in logs
assert "WARNING" in logs

def test_mock_training_data_fixture(self, mock_training_data):
"""Verify mock_training_data fixture provides expected data."""
assert "images" in mock_training_data
assert "labels" in mock_training_data
assert "batch_size" in mock_training_data
assert "num_epochs" in mock_training_data

assert len(mock_training_data["images"]) == 3
assert len(mock_training_data["labels"]) == 3
assert mock_training_data["batch_size"] == 2
assert mock_training_data["num_epochs"] == 5


def test_standalone_function():
"""Verify standalone test functions work outside of classes."""
assert 2 + 2 == 4
Empty file added tests/unit/__init__.py
Empty file.