Metadata-Version: 2.5
Name: autohilt
Version: 0.6.0
Summary: autoHILT test executor — hardware-in-loop pytest fixtures generated from your drawn test station
Project-URL: Homepage, https://autohilt.com
Project-URL: Documentation, https://autohilt.com
Author-email: RML Consulting <hello@autohilt.com>
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Requires-Dist: bidict>=0.22
Requires-Dist: pyserial>=3.5
Requires-Dist: pytest<9,>=7
Requires-Dist: tomli>=2; python_version < '3.11'
Requires-Dist: zstandard>=0.21
Description-Content-Type: text/markdown

# autohilt

The test engine for [autoHILT](https://autohilt.com) — automated
hardware-in-loop testing for embedded systems.

autoHILT turns a drawing of your test station into pytest fixtures: draw the
station (MCUs on their board, debugger, relays, power) on the autoHILT canvas,
publish it, and this package brings those components to life as fixtures named
after your own drawing — `my_mcu`, `cli`, `dut_power` — in plain, unmodified
pytest.

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install autohilt
autohilt init            # the first-run wizard: repo, login, config, starter test
pytest --ah-dev          # iterate — results are never recorded
```

Every test computer carries a built-in `greeter` endpoint, so the whole loop
works before any hardware arrives:

```python
def test_hello(greeter):
    assert greeter.ping().startswith("hello from my-rig")
```

Plain `pytest` is a **production run** — recorded against your workspace — so
it insists on a tree it can trace (committed, pushed, credentialed) and names
every missing precondition instead of quietly downgrading the run.

## Commands

| command | what it does |
| --- | --- |
| `autohilt init` | first-run wizard: set up or connect a test repo, sync, write the starter test and starting libraries |
| `autohilt sync` | resolve the pinned config, cache its parts list, write `autohilt.lock` — and say when the drawing has outgrown your tree |
| `autohilt check` | full local-state report; every ✗ names its fix |
| `autohilt link` | hold this machine's connection to the server: scheduled runs arrive down it, health is reported on the server's interval, and the station reads online while it runs (`--install-service` to keep it up across reboots) |
| `autohilt scaffold` | write starting libraries: per-board product libraries in your company package, per-kind wrapper modules in `libraries/autohilt/` (report-only where files exist) |
| `autohilt discover` | identify what is plugged into THIS machine and auto-write its device map (`autohilt.local.toml`): matches the bus against the drawing, walks you through anything ambiguous, never writes to a device |
| `autohilt docs` | print the in-wheel module reference — always the version you are running; `--path` for agents reading the files directly |
| `autohilt migrate` | after a rename on the canvas, rewrite your tests to match |
| `autohilt login` | store a personal API token for a server (browser approval on a terminal) |
| `autohilt enroll` | give this machine its own station credential (`--code` for rack machines nobody logs in on) |
| `autohilt unenroll` | hand this machine's credential back |
| `autohilt git-credential` | git credential helper `autohilt init` wires repo-locally — not a command you run |

`autohilt --version` prints the engine version.

## The fixture model, in brief

Fixtures are **minted in memory** from your locked parts list at pytest
startup — nothing generated is ever code at rest, and a fixture is named
exactly what you named the component on the canvas. Many-instance kinds get
one kind-named **factory** (`relay.get("mains")`, `relay.get(model=RelayModel.NUMATO)`
— enums, never strings). Boards are **composition units**: a PCBA fixture
carries the transports the drawing wires across its boundary (`board.cli`,
`board.power["mains_feed"]`, `board.debug`). Roles appear when unambiguous:
`dut`, `cli`, `dut_power`.

## The lifecycle, in brief

Initialization is **collection-driven**: only what the selected tests need is
brought up, made test-ready, and **baselined** — power channels driven to the
resting state your drawing declares (`default_on`), then recorded. Every write
through the engine is **tracked per channel**; after every test the restore is
**targeted** — only what the test drifted is driven back — with your own
`restore()` hook running every time. A failed or hung restore **poisons** the
resource and the next test gets a clean refusal, never a silent re-init. At
session teardown, `on_final_release()` runs (system first, then each
component) before drivers close. Everything the engine calls is time-bounded
(10 s default). The normative contract lives in `autohilt docs lifecycle`.

## Your test tree

```
tests/  drafts/  fixtures/  credentials/  pytest.ini  requirements.txt
libraries/
  autohilt/        kind wrappers — generic extensions of the engine's classes
  <your company>/  product layer — boards, system.py, subsystems
  third_party/     vendor and community wrappers you pull in
```

Each fixture comes with a library: the scaffold starts a wrapper class per
drawn instance (extending `RelayBase`, `DebuggerBase`, `PcbaBase`, …), you
grow it, and the fixture yields **your** class — with the engine's restore,
poison, timeout, and release rules applying to your code identically. The
files are yours after the scaffold writes them: never regenerated, never
edited; when the drawing grows, missing pieces are created and existing files
get paste-ready additions printed instead. `autohilt docs tree` has the full
map.

## Module reference

Ships inside the wheel — `autohilt docs <topic>`: `lifecycle`, `tree`,
`factories`, `virtual`, `pcba`, `relay`, `usb_serial`, `debugger`, `usb_hub`,
`custom`, `transports`, `changelog`.
Each kind topic states its API, restore behavior, timeouts, extension points,
what is guaranteed versus not, and its exact failure messages. Agents working
in your repo can read the same files straight from `autohilt docs --path`.

## Requirements & platforms

Python 3.9+, pytest 7 or 8. Linux, macOS, Windows, and Raspberry Pi (arm64)
are all first-class: the engine and its built-in driver layer (over
pyserial) are pure Python, Windows COM ports are handled, and the test
suite runs on all three OS families in CI. Hardware drivers bind to each
machine's own ports through a gitignored `autohilt.local.toml` device map —
the drawing stays shared; the paths stay local.

The full walkthrough — account to recorded run in about ten minutes, no
hardware required — is at [autohilt.com](https://autohilt.com); signed-in
users find the guides under **Documentation** in the app.

- Homepage: https://autohilt.com
- Contact: hello@autohilt.com
