# Packaging a SimReady Test Pack

Authoritative source: `core/discovery.py:discover_test_dirs()` (the framework
side that loads test packs).

## Minimal pyproject.toml

```toml
[project]
name            = "oem-acme-tests"
version         = "0.1.0"
description     = "OEM-specific benchmarks for ACME robotic assets"
requires-python = ">=3.11"
dependencies    = [
    "simready-benchmark>=2026.5.1",
]

[project.entry-points."simready_benchmark.tests"]
acme = "oem_acme_tests"

[build-system]
requires      = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
package-dir = {"" = "src"}
```

The entry-point group is literally `simready_benchmark.tests`. The value is the
**bare module path** of your package (`oem_acme_tests`), with **no `:callable`
suffix**. The framework calls `entry_point.load()`, gets back the module,
and uses `os.path.dirname(module.__file__)` as the directory to walk
recursively for `@test`-decorated functions.

The left-hand key (`acme` in the example) is an arbitrary unique name; the
framework lists it with `simready-benchmark --list-tests` for diagnostics.

## Source Layout

```
oem-acme-tests/
├── pyproject.toml
└── src/
    └── oem_acme_tests/
        ├── __init__.py             # can be empty; just makes the module importable
        ├── fet028_gripper/
        │   ├── __init__.py
        │   └── close_and_lift.py   # @test-decorated functions
        └── fet003_physics/
            ├── __init__.py
            └── ground_drop.py
```

`src/` layout (with `package-dir = {"" = "src"}` in pyproject) is conventional;
not required by the framework. The framework only needs the entry point to
resolve to an importable module whose `__file__` lives inside the source tree.

## File Discovery Rules

The framework's `discover_tests(test_dirs)` walks each directory and imports
any `.py` file that does NOT start with `_`. Files starting with underscore
(`_helpers.py`, `__init__.py`) are skipped. Module-import errors are logged
but do not abort discovery.

## Version-Pinning the Framework Dependency

Pin against a minimum framework version:

```toml
dependencies = ["simready-benchmark>=2026.5.1"]
```

This avoids unnecessary re-publishes when `simready-benchmark` ships a patch
release. If you use APIs only available in a specific version, bump the floor.
Kit-using packs should also depend on the plugin extra:

```toml
dependencies = [
    "simready-benchmark[kit]>=2026.5.1",
]
```

## Distribute through PyPI

```bash
python -m build              # writes dist/oem_acme_tests-0.1.0-py3-none-any.whl
twine upload dist/*          # or your organization's internal Artifactory
```

End users install with:

```bash
pip install oem-acme-tests
simready-benchmark --list-tests   # confirms the pack's tests are discovered
```

## Local Development Without Packaging

Tell the framework where to find your test pack on disk:

```toml
# engines.toml
[tests]
paths = ["/path/to/oem-acme-tests/src/oem_acme_tests"]
```

Or use the CLI or env var. Highest priority first: `--tests-path` > `SIMREADY_BENCHMARK_TESTS_PATH` > `engines.toml [tests].paths` > `project_config.toml [tests].paths`. The first non-empty source wins; pip-installed entry-point packs (and, for a foundations project, the bundled FET default) always merge on top.

```bash
simready-benchmark --tests-path /path/to/oem-acme-tests/src/oem_acme_tests

# Linux/macOS
export SIMREADY_BENCHMARK_TESTS_PATH=/path/to/oem-acme-tests/src/oem_acme_tests:/path/to/another

# Windows PowerShell
$env:SIMREADY_BENCHMARK_TESTS_PATH = "C:/path/to/oem-acme-tests/src/oem_acme_tests;C:/path/to/another"
```

Path-discovered tests COMBINE with entry-point-discovered tests; they do not
replace them. After your pack stabilizes, package it as a wheel and the
engines.toml override is no longer needed.

## When to Ship a New Framework Version

The plan / result.json on-disk schemas are versioned by
`simready_benchmark.PROTOCOL_VERSION`. If a test pack uses an API that requires a
newer protocol than the user's installed framework, the runner will refuse
to execute the plan. Bump the `simready-benchmark>=...` floor in your
`pyproject.toml` dependencies whenever you adopt a new framework feature.
