Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
jobs:
validate:
runs-on: ubuntu-latest
env:
PYTHONDONTWRITEBYTECODE: 1
steps:
- uses: actions/checkout@v5

Expand Down Expand Up @@ -50,6 +52,8 @@ jobs:
defaults:
run:
shell: bash
env:
PYTHONDONTWRITEBYTECODE: 1
steps:
- name: Check out repository
uses: actions/checkout@v5
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,15 @@ ruff-noqa: ## Runs Ruff, adding noqa comments to disable warnings
type-check: ## Run ty type checker
@$(UV) run --no-sync ty check

# PYTHONDONTWRITEBYTECODE is set inline for test targets to prevent .pyc generation
# during testing, which reduces I/O overhead. It's NOT set globally to avoid affecting
# dev servers, docker builds, and other targets that benefit from .pyc caching.
test: ## Run the tests
@$(UV) run --no-sync pytest
@PYTHONDONTWRITEBYTECODE=1 $(UV) run --no-sync pytest

coverage: ## Run the tests and generate coverage report
@$(UV) run --no-sync pytest --cov=byte_bot
@$(UV) run --no-sync coverage html
@PYTHONDONTWRITEBYTECODE=1 $(UV) run --no-sync pytest --cov=byte_bot
@PYTHONDONTWRITEBYTECODE=1 $(UV) run --no-sync coverage html
@$(UV) run --no-sync coverage xml

check-all: lint type-check fmt test ## Run all linting, formatting, and tests
Expand Down
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ dev = [
"pytest-mock>=3.12.0",
"hypothesis>=6.92.0",
"pytest-asyncio>=0.23.2",
"pytest-socket>=0.7.0",
"pytest-xdist>=3.6.1",
# - Documentation
"sphinx>=7.2.6",
"sphinx-autobuild>=2021.3.14",
Expand Down Expand Up @@ -92,6 +94,7 @@ python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
env_files = [".env.test"]
norecursedirs = [".*", "*.egg-info", ".git", ".tox", "node_modules", "worktrees", "docs", ".venv", "htmlcov"]
addopts = [
"-v",
"--strict-markers",
Expand All @@ -101,11 +104,17 @@ addopts = [
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
"-p", "no:doctest",
"-p", "no:pastebin",
"-p", "no:legacypath",
"--disable-socket",
"--allow-unix-socket",
]
markers = [
"asyncio: mark test as async",
"unit: mark test as unit test",
"integration: mark test as integration test",
"enable_socket: mark test as needing network access",
]
filterwarnings = [
"ignore::DeprecationWarning:pkg_resources.*",
Expand Down
88 changes: 85 additions & 3 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,92 @@ uv add --dev aiosqlite

## Performance

Current test execution time: ~0.4-0.6 seconds for all 75 tests
Current test execution time (1036 tests total):
- **Collection**: ~1.18s
- **Sequential execution**: ~23s
- **Parallel execution** (`pytest -n auto`): ~10s (58% faster)

- Unit tests: ~0.1s
- Integration tests: ~0.3s (includes database setup/teardown)
### Performance Optimizations

Following best practices from [awesome-pytest-speedup](https://github.com/zupo/awesome-pytest-speedup), we've implemented several optimizations:

#### 1. PYTHONDONTWRITEBYTECODE=1
- **Impact**: Reduces I/O overhead during test runs
- **Location**: Scoped to test targets in `Makefile`, set globally in `.github/workflows/ci.yml`
- Prevents `.pyc` file generation during testing
- **Note**: NOT set globally to avoid affecting dev servers, Docker builds, and other targets that benefit from .pyc caching

#### 2. Disabled Unnecessary Builtin Plugins
- **Impact**: Faster collection
- **Disabled**: `doctest`, `pastebin`, `legacypath`
- **Config**: `pyproject.toml` → `addopts`

#### 3. Collection Optimization
- **Impact**: 25% faster collection
- **Method**: `norecursedirs` excludes `.git`, `node_modules`, `docs`, `.venv`, etc.
- **Config**: `pyproject.toml` → `norecursedirs`

#### 4. Network Access Prevention (pytest-socket)
- **Impact**: Catches inadvertent network calls in unit tests
- **Usage**: Tests needing network access: `@pytest.mark.enable_socket`
- **Config**: `--disable-socket --allow-unix-socket` (allows DB connections)

#### 5. Parallel Execution (pytest-xdist)
- **Impact**: 58% faster execution on multi-core systems
- **Usage**: `pytest -n auto` (opt-in, not default)
- **Note**: Some tests may have race conditions when run in parallel

### Performance Comparison

```
┌─────────────────────────────────────────────────────────────┐
│ Pytest Performance Metrics (1036 tests) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Collection Time: │
│ ├─ Before: 1.58s ████████████████ │
│ └─ After: 1.18s ███████████▓ (-25%) │
│ │
│ Test Execution: │
│ ├─ Sequential: ~23s ██████████████████████████████████ │
│ └─ Parallel: ~10s █████████████▓ (-58%) │
│ │
│ Total Time (with parallel): │
│ ├─ Before: ~25s ██████████████████████████████████ │
│ └─ After: ~11s ██████████████▓ (-56%) │
│ │
└─────────────────────────────────────────────────────────────┘
```

### Running Tests with Parallelization

```bash
# Default: Sequential (safer, no race conditions)
make test

# Parallel: Use all CPU cores (faster)
pytest -n auto

# Parallel: Use specific number of workers
pytest -n 4

# Quick feedback: Run only failed tests
pytest --lf

# Quick feedback: Run failed first, then rest
pytest --ff
```

**Note**: If tests fail with `-n auto` but pass sequentially, this indicates race conditions or shared state between tests.

### Future Optimization Opportunities

Based on [awesome-pytest-speedup](https://github.com/zupo/awesome-pytest-speedup):

1. **Database optimization**: Use transaction rollback pattern instead of recreation
2. **Selective execution**: `pytest-testmon` to run only tests affected by code changes
3. **Test categorization**: `pytest-skip-slow` to skip slow tests by default
4. **CI parallelization**: `pytest-split` to distribute tests across multiple CI runners

## Documentation

Expand Down
36 changes: 36 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading