# CLAUDE.md - oteltest Repository Guide

## Project Overview

**oteltest** is a Python testing framework for black-box testing of OpenTelemetry (OTel) client implementations. It enables developers to write integration tests that verify telemetry (traces, metrics, logs) is correctly exported from Python scripts to OpenTelemetry Collector endpoints.

**Version:** 0.44.0

## Guiding Principles

1. **Ease of use**: The library must be foolproof. Users should not need to know implementation details or set complex environment variables manually. The framework should handle configuration automatically when possible.

2. **Backward compatibility**: Changes must not break existing tests. When users update their oteltest library, their existing test scripts must continue to work without modification. New methods should have sensible defaults.

## Repository Structure

```
oteltest/
├── src/oteltest/              # Main package
│   ├── __init__.py            # OtelTest abstract base class
│   ├── main.py                # CLI entry point: oteltest command
│   ├── private.py             # Core orchestration logic
│   ├── telemetry.py           # Telemetry data models and query helpers
│   ├── sink/                  # OTel Collector-like server
│   │   ├── __init__.py        # GrpcSink & HttpSink implementations
│   │   ├── main.py            # CLI entry point: otelsink command
│   │   ├── handler.py         # RequestHandler interface
│   │   └── private.py         # gRPC service implementations
│   └── viz/                   # Web visualization for telemetry
│       ├── __init__.py        # VizApp Flask application
│       ├── main.py            # CLI entry point: otelviz command
│       └── templates/         # Jinja2 HTML templates
├── tests/                     # Unit & integration tests
├── example_scripts/           # Example test scripts
├── baseline_tests/            # Baseline integration tests
└── pyproject.toml             # Project config & dependencies
```

## Key Components

### OtelTest Framework (`src/oteltest/`)
- `OtelTest`: Abstract base class that test scripts implement
- Key methods: `requirements()`, `environment_variables()`, `wrapper_command()`, `is_http()`, `on_start()`, `on_stop()`

### Sink (`src/oteltest/sink/`)
- `GrpcSink`: gRPC server on port 4317
- `HttpSink`: HTTP server on port 4318 (supports both protobuf and JSON content types)
- `AccumulatingHandler`: Collects telemetry for test validation

### Telemetry (`src/oteltest/telemetry.py`)
- `Request`, `Telemetry`: Data structures for collected telemetry
- Helper functions: `count_spans()`, `get_spans()`, `get_metric_names()`, etc.

### Visualization (`src/oteltest/viz/`)
- Flask web app for browsing telemetry JSON files
- Routes: `/` (file list), `/trace/<filename>` (detail view)

## Commands

```bash
# Run tests against a script
oteltest [OPTIONS] script_path [script_path ...]
  -d, --venv-parent-dir DIR    Override venv directory
  -j, --json-dir DIR           Output directory for telemetry JSON
  -v, --verbose                Enable debug logging

# Standalone sink server
otelsink [--http]

# Visualization server (port 8888)
otelviz trace_dir
```

## Development Commands

```bash
# Install dependencies
pip install -e .

# Run tests
pytest tests/

# Run with hatch
hatch run test
```

## Test Execution Flow

1. Load test class (dynamic import, finds class inheriting `OtelTest` or with "OtelTest" in name)
2. Start sink (gRPC 4317 or HTTP 4318 based on `is_http()`)
3. Create isolated virtual environment
4. Install dependencies from `requirements()`
5. Run script subprocess with `wrapper_command()` and `environment_variables()`
6. Call `on_start()` (returns timeout or None)
7. Collect telemetry from sink
8. Call `on_stop(tel, stdout, stderr, returncode)` for assertions
9. Save telemetry JSON (incremental: script.0.json, script.1.json, etc.)

## Writing Tests

```python
class MyOtelTest:
    def requirements(self):
        return ["opentelemetry-distro", "opentelemetry-exporter-otlp-proto-http"]

    def environment_variables(self):
        return {"OTEL_SERVICE_NAME": "my-service"}

    def wrapper_command(self):
        return "opentelemetry-instrument"

    def is_http(self):
        return True

    def on_start(self):
        return 30  # Timeout in seconds, or None to wait for natural exit

    def on_stop(self, tel, stdout, stderr, returncode):
        from oteltest.telemetry import count_spans
        assert count_spans(tel) > 0

if __name__ == "__main__":
    # Application code here
    pass
```

## Important Notes

- **Always activate venv before running**: `source .venv/bin/activate && oteltest ...`
- Port 4317 (gRPC) or 4318 (HTTP) must be available
- `on_start()` and `on_stop()` run in oteltest's process, not the subprocess
- Test class discovery: inherits `OtelTest` OR has "OtelTest" in class name

## Dependencies

- opentelemetry-proto, opentelemetry-api
- grpcio, protobuf
- flask
- Python 3.8-3.12 supported
