Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
83 changes: 25 additions & 58 deletions .claude/skills/run-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Use this skill when you have:
## Key Principles

1. **Always use the run-tests skill** when testing code changes - it's optimized for intelligent suite discovery
2. **Never run pytest directly** - bypasses the project's test infrastructure (use `scripts/run-tests` or `riot` via `scripts/ddtest`)
2. **Never run pytest directly** - use `scripts/run-tests`
3. **Minimal venvs for iteration** - run 1-2 venvs initially, expand only if needed
4. **Use `--dry-run` first** - see what would run before executing
5. **Follow official docs** - `docs/contributing-testing.rst` is the source of truth for testing procedures
Expand All @@ -54,7 +54,7 @@ scripts/run-tests --list <edited-files>
This outputs JSON showing:
- Available test suites that match your changed files
- All venvs (Python versions + package combinations) available for each suite
- Their hashes, Python versions, and package versions
- Their descriptive IDs, Python versions, and package versions

### Step 3: Intelligently Select Venvs

Expand Down Expand Up @@ -84,41 +84,24 @@ When you modify files like:
#### For Test-Only Changes
When you modify `tests/` files (but not test infrastructure):
- Run only the specific test files/functions modified
- Use pytest args with two separators: `-- -- -k test_name` (first `--` ends `scripts/run-tests` parsing and starts riot args; second `--` tells riot to forward remaining args to pytest), or direct pytest test paths (e.g., `-- -- tests/contrib/flask/test_views.py`)
- Pass pytest arguments after one separator, for example `-- -k test_name`

#### For Test Infrastructure Changes
When you modify:
- `tests/conftest.py`, `tests/suitespec.yml`, `scripts/run-tests`, `riotfile.py`
- `tests/conftest.py`, suitespec files, `scripts/run-tests`, or `.uv` locks

**Strategy:** Run a quick smoke test suite
- Example: `internal` suite with 1 venv as a sanity check
- Or run small existing test suites to verify harness changes

### Step 4: Execute Selected Venvs
### Step 4: Execute Selected Environments

I'll run the selected venvs. On the **first invocation in a session**, always run without `-s` to ensure the venv has dd-trace-py properly installed:
Run the selected descriptive environment IDs. The runner creates the uv environment, installs dd-trace-py, installs its exact lock, and manages required services:

```bash
scripts/run-tests --venv <hash1> --venv <hash2>
scripts/run-tests --venv <environment-id-1> --venv <environment-id-2>
```

On **subsequent runs**, use `-s` (riot's `--skip-base-install` flag, not to be confused with pytest's `-s`) to skip rebuilding dd-trace-py and save significant time:

```bash
scripts/run-tests --venv <hash1> --venv <hash2> -- -s
```

**When to use `-s` (skip base install) on subsequent runs:**
- Only Python files were modified (no native code changes)
- Iterating on test fixes within the same session
- Re-running tests after small code tweaks

**When to omit `-s` even on subsequent runs (force rebuild):**
- After merging or rebasing from main (dependencies or native code may have changed)
- C extensions, Cython (`.pyx`, `.pxd`), or CMake files were modified (e.g., under `ddtrace/internal/`, `ddtrace/appsec/_iast/_taint_tracking/`, `src/native/`)
- `setup.py`, `pyproject.toml`, or `setup.cfg` were modified
- `riotfile.py` or `.riot/requirements/` files were modified

This will:
- Start required Docker services (redis, postgres, etc.)
- Run tests in the specified venvs sequentially
Expand All @@ -136,9 +119,9 @@ This will:
- Offer to run specific failing tests with more verbosity
- Help iterate on fixes and re-run

For re-running specific tests (use `-s` since the venv is already built):
For re-running specific tests:
```bash
scripts/run-tests --venv <hash> -- -s -- -vv -k test_name
scripts/run-tests --venv <environment-id> -- -vv -k test_name
```

## When Tests Fail
Expand All @@ -153,7 +136,7 @@ When you encounter test failures, follow this systematic approach:

## Venv Selection Strategy in Detail

### Understanding Venv Hashes
### Understanding Environment IDs

From `scripts/run-tests --list`, you'll see output like:

Expand All @@ -164,12 +147,12 @@ From `scripts/run-tests --list`, you'll see output like:
"name": "tracer",
"venvs": [
{
"hash": "abc123",
"id": "tracer-py39",
"python_version": "3.8",
"packages": "..."
},
{
"hash": "def456",
"id": "tracer-py314",
"python_version": "3.11",
"packages": "..."
}
Expand Down Expand Up @@ -204,10 +187,10 @@ From `scripts/run-tests --list`, you'll see output like:

### Using `--venv` Directly

When you have a specific venv hash you want to run, you can use it directly without specifying file paths:
When you have a specific environment ID, run it directly without specifying file paths:

```bash
scripts/run-tests --venv e06abee
scripts/run-tests --suite contrib::flask --venv flask-py313-flask-latest
```

The `--venv` flag automatically searches **all available venvs** across all suites, so it works regardless of what files you have locally changed. This is useful when:
Expand All @@ -227,13 +210,8 @@ scripts/run-tests --list ddtrace/contrib/internal/flask/patch.py

# Select output (latest Python):
# Suite: contrib::flask
# Venv: hash=e06abee, Python 3.13, flask

# First run: no -s to ensure venv is properly set up
scripts/run-tests --venv e06abee

# Subsequent runs: use -s since only Python files changed
scripts/run-tests --venv e06abee -- -s
# Environment: flask-py313-flask-latest, Python 3.13, Flask latest
scripts/run-tests --suite contrib::flask --venv flask-py313-flask-latest
```

### Example 2: Fixing a Core Tracing Issue
Expand All @@ -248,11 +226,7 @@ scripts/run-tests --list ddtrace/_trace/tracer.py
# - tracer: latest Python (e.g., abc123)
# - internal: latest Python (e.g., def456)

# First run: no -s
scripts/run-tests --venv abc123 --venv def456

# Subsequent runs: use -s since only Python files changed
scripts/run-tests --venv abc123 --venv def456 -- -s
scripts/run-tests --venv tracer-py314 --venv internal-py314
```

### Example 3: Fixing a Test-Specific Bug
Expand All @@ -263,27 +237,22 @@ scripts/run-tests --venv abc123 --venv def456 -- -s
scripts/run-tests --list tests/contrib/flask/test_views.py
# Output shows: contrib::flask suite

# First run: no -s
scripts/run-tests --venv flask_py311 -- -- -vv tests/contrib/flask/test_views.py

# Subsequent runs: use -s to skip rebuild
scripts/run-tests --venv flask_py311 -- -s -- -vv tests/contrib/flask/test_views.py
scripts/run-tests --suite contrib::flask --venv flask-py311-flask-latest -- -vv tests/contrib/flask/test_views.py
```

### Example 4: Iterating on a Failing Test

After the first run shows a test failing, use `-s` to iterate quickly:
After the first run shows a test failing, narrow the pytest selection:

```bash
scripts/run-tests --venv flask_py311 -- -s -- -vv -k test_view_called_twice
scripts/run-tests --suite contrib::flask --venv flask-py311-flask-latest -- -vv -k test_view_called_twice
# Focused on the specific failing test with verbose output
```

## Best Practices

### DO ✅

- **Use `-s` on subsequent runs**: After the first run builds the venv, pass `-- -s` to skip rebuild when only Python files changed
- **Start small**: Run 1 venv first, expand only if needed
- **Be specific**: Use pytest `-k` filter when re-running failures
- **Check git**: Verify you're testing the right files with `git status`
Expand All @@ -292,8 +261,6 @@ scripts/run-tests --venv flask_py311 -- -s -- -vv -k test_view_called_twice

### DON'T ❌

- **Use `-s` after merging from main**: Native code or dependencies may have changed, requiring a rebuild
- **Use `-s` when C/Cython/CMake files changed**: Native extensions must be recompiled
- **Run all venvs initially**: That's what CI is for
- **Skip the minimal set guidance**: It's designed to save you time
- **Ignore service requirements**: Some suites need Docker services up
Expand All @@ -310,7 +277,7 @@ scripts/run-tests --venv flask_py311 -- -s -- -vv -k test_view_called_twice
- Where to put tests in the repository
- Prerequisites (Docker, uv)
- Complete `scripts/run-tests` usage examples
- Riot environment management details
- uv environment and lock management details
- Running specific test files and functions
- Test debugging strategies

Expand Down Expand Up @@ -354,10 +321,10 @@ docker compose down

The `scripts/run-tests` system:
- Maps source files to test suites using patterns in `tests/suitespec.yml`
- Uses `riot` to manage multiple Python/package combinations as venvs
- Expands suitespec matrices into uv environments with committed locks
- Each venv is a self-contained environment
- Docker services are managed per suite lifecycle
- Use `-- <riot args> -- <pytest args>` for mixed passthrough. The first `--` is consumed by `scripts/run-tests`, and the second `--` is consumed by `riot` before forwarding remaining args to pytest. If you only need pytest args, use `-- -- <pytest args>`.
- Pass test-command arguments after one `--` separator.

### Supported Suite Types

Expand Down Expand Up @@ -386,14 +353,14 @@ You can limit CPU and memory resources to simulate resource-constrained CI envir
**Usage:**
```bash
# Run tests with resource constraints
DD_TEST_CPUS=0.5 DD_TEST_MEMORY=1g scripts/run-tests --venv <hash>
DD_TEST_CPUS=0.5 DD_TEST_MEMORY=1g scripts/run-tests --venv <environment-id>

# Run specific test file with heavy constraints
DD_TEST_CPUS=0.25 DD_TEST_MEMORY=1g scripts/run-tests tests/path/to/test.py

# Multiple runs to catch intermittent failures
for i in {1..10}; do
DD_TEST_CPUS=0.5 DD_TEST_MEMORY=1g scripts/run-tests --venv <hash> -- --randomly-seed=$RANDOM
DD_TEST_CPUS=0.5 DD_TEST_MEMORY=1g scripts/run-tests --venv <environment-id> -- --randomly-seed=$RANDOM
done
```

Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ releasenotes/ @DataDog/apm-python
tests/snapshots/ @DataDog/apm-python
riotfile.py @DataDog/apm-python
.riot/requirements/ @DataDog/apm-python
.uv/ @DataDog/python-guild
CHANGELOG.md @DataDog/apm-python
ddtrace/internal/telemetry/ @DataDog/apm-python
tests/telemetry @DataDog/apm-python
Expand Down
8 changes: 4 additions & 4 deletions .github/PULL_REQUEST_TEMPLATE/python_315_bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ This PR enables the **`<integration>`** integration on Python 3.15.
<!-- One-paragraph summary of the upstream blocker and what changed.
Example: "Bumps `tiktoken` to 0.x.y, which adds Python 3.15 wheels
(https://github.com/openai/tiktoken/releases/tag/x.y). Lifts the
`max_version="3.13"` cap on the `<integration>` venv in riotfile.py." -->
Python 3.13 cap in the `<integration>` suitespec matrix." -->

## Checklist

- [ ] Bumped upstream pin in `riotfile.py` to a version that supports Python 3.15
- [ ] Lifted `max_version="3.13"` / `"3.14"` cap on the affected venv(s) (if present)
- [ ] Ran `riot generate <suite-pattern>` and committed the regenerated `.riot/requirements/*.txt` lockfiles
- [ ] Bumped the upstream pin in suitespec to a version that supports Python 3.15
- [ ] Added Python 3.15 to the affected matrix cases where supported
- [ ] Ran `scripts/test-env lock <suite>` and committed the regenerated `.uv/*.txt` locks
- [ ] Ran the suite locally on 3.15 via `scripts/run-tests <suite>` (paste a link or summary of the result)
- [ ] Updated `supported_versions.json` if integration min/max versions changed
- [ ] Release note added under `releasenotes/notes/`, **or** PR labeled `changelog/no-changelog` (test/CI-only changes)
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/generated-change-patch/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ runs:

case "$PROFILE" in
package-versions)
if [[ "$path" =~ ^\.riot/requirements/[^/]+\.txt$ ]] ||
if [[ "$path" =~ ^\.uv/[^/]+\.txt$ ]] ||
[[ "$path" == "supported_versions.json" ]] ||
[[ "$path" == "scripts/integration_registry/registry.yaml" ]]; then
return
Expand Down
42 changes: 12 additions & 30 deletions .github/workflows/generate-package-versions.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: Update riot lockfiles
name: Update test locks

on:
workflow_dispatch: # can be triggered manually
schedule:
- cron: "0 0 * * *" # daily at midnight

jobs:
update-riot-lockfiles:
name: Update riot lockfiles
update-test-locks:
name: Update test locks
if: github.event_name != 'schedule' || github.event.repository.fork == false
runs-on: ubuntu-22.04
outputs:
Expand Down Expand Up @@ -79,26 +79,8 @@ jobs:
run: |
curl https://dd-trace-py-builds.s3.amazonaws.com/main/install.sh | bash

- name: Compute supply-chain cooldown cutoff
id: cooldown
run: |
# TEST-CD (APMLP-1362): forward a cutoff 48h in the past to riot's
# uv pip compile backend so transitive deps younger than the
# cooldown can't be pulled into a regenerated lockfile.
CUTOFF=$(date -u -d "2 days ago" "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|| python -c "import datetime as dt; print((dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=2)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
echo "cutoff=${CUTOFF}" >> "$GITHUB_OUTPUT"
echo "Using exclude-newer cutoff: ${CUTOFF}"

- name: Run regenerate-riot-latest
env:
# Opt riot's `requirements` compile step into `uv pip compile` so
# that --exclude-newer can be forwarded for the cooldown. Older
# riot versions ignore both variables silently, so this is safe
# to set before the riot upgrade lands.
RIOT_PIP_COMPILE_BACKEND: "uv"
RIOT_PIP_COMPILE_EXCLUDE_NEWER: ${{ steps.cooldown.outputs.cutoff }}
run: scripts/regenerate-riot-latest.sh
- name: Regenerate test locks
run: scripts/regenerate-test-locks-latest.sh

- name: Run integration registry update
run: python scripts/integration_registry/update_and_format_registry.py
Expand Down Expand Up @@ -128,8 +110,8 @@ jobs:

create-pull-request:
name: Create pull request
needs: update-riot-lockfiles
if: needs.update-riot-lockfiles.outputs.changed == 'true'
needs: update-test-locks
if: needs.update-test-locks.outputs.changed == 'true'
runs-on: ubuntu-22.04
permissions:
actions: read
Expand Down Expand Up @@ -166,14 +148,14 @@ jobs:
with:
token: ${{ steps.octo-sts.outputs.token }}
sign-commits: true
branch: "upgrade-latest-${{ needs.update-riot-lockfiles.outputs.venv_name }}-version"
branch: "upgrade-latest-${{ needs.update-test-locks.outputs.venv_name }}-version"
commit-message: "Update package version"
delete-branch: true
base: main
title: "chore: update ${{ needs.update-riot-lockfiles.outputs.venv_name }} latest version to ${{ needs.update-riot-lockfiles.outputs.new_latest }}"
title: "chore: update ${{ needs.update-test-locks.outputs.venv_name }} latest version to ${{ needs.update-test-locks.outputs.new_latest }}"
labels: changelog/no-changelog
body: |
Update ${{ needs.update-riot-lockfiles.outputs.venv_name }} lockfiles and dependency package lockfiles.
Update ${{ needs.update-test-locks.outputs.venv_name }} lockfiles and dependency package lockfiles.
This performs the following updates:
1) Some ${{ needs.update-riot-lockfiles.outputs.venv_name }} lockfiles use ${{ needs.update-riot-lockfiles.outputs.venv_name }} `latest`. This will update ${{ needs.update-riot-lockfiles.outputs.venv_name }} and dependencies.
2) Some ${{ needs.update-riot-lockfiles.outputs.venv_name }} lockfiles use a pinned (non-latest) version of ${{ needs.update-riot-lockfiles.outputs.venv_name }}, but require the `latest` version of another package. This will update all such packages.
1) Some ${{ needs.update-test-locks.outputs.venv_name }} locks use ${{ needs.update-test-locks.outputs.venv_name }} `latest`. This updates that package and its dependencies.
2) Some locks pin ${{ needs.update-test-locks.outputs.venv_name }} but use the latest version of another package. This updates those dependencies.
Loading
Loading