pytest-hexaqueue 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_hexaqueue-0.6.0/PKG-INFO +218 -0
- pytest_hexaqueue-0.6.0/README.md +203 -0
- pytest_hexaqueue-0.6.0/pyproject.toml +31 -0
- pytest_hexaqueue-0.6.0/pyproject.toml.orig +28 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/__init__.py +74 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/adapters/__init__.py +17 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/adapters/client.py +188 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/adapters/slicer.py +127 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/domain/__init__.py +21 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/domain/delta.py +38 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/domain/outcome.py +101 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/domain/slice.py +109 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/infra/__init__.py +17 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/infra/plugin.py +516 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/ports/__init__.py +9 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/ports/client.py +48 -0
- pytest_hexaqueue-0.6.0/src/pytest_hexaqueue/ports/slicer.py +39 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-hexaqueue
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: pytest-hexaqueue - Distributed Test Slicing, Remote Execution, and Multi-CSP Free-Tier Runner Plugin
|
|
5
|
+
Author: Richard West
|
|
6
|
+
Author-email: Richard West <dopplereffect.us@gmail.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Requires-Dist: hexaqueue-collateral
|
|
9
|
+
Requires-Dist: hexaqueue-core
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: pydantic>=2.10.0
|
|
12
|
+
Requires-Dist: pytest>=8.3.0
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# 🧪 `pytest-hexaqueue`
|
|
17
|
+
|
|
18
|
+
> Distributed Test Execution, Slicing Engine, and Multi-CSP Free-Tier Bursting Plugin for Pytest.
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/pytest-hexaqueue/)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
[](https://dopplereffect.us/hexaqueue/)
|
|
23
|
+
[](https://dopplereffect.us/hexastack/)
|
|
24
|
+
[](https://dopplereffect.us/hexaqual/)
|
|
25
|
+
[](../../LICENSE)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🎯 Overview
|
|
30
|
+
|
|
31
|
+
`pytest-hexaqueue` is a first-class distributed test execution plugin for `pytest`. It partitions large test suites into balanced execution slices and fans out execution across **Hexaqueue cluster workers** and federated **Multi-CSP Free-Tier Meshes** (OCI, GCP, AWS, Azure, and local nodes), achieving horizontal test scalability with zero cloud spend ($0.00 spend ceiling).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ⚖️ Coexistence & Comparison: `pytest-hexaqueue` vs. `pytest-xdist`
|
|
36
|
+
|
|
37
|
+
A common question is how `pytest-hexaqueue` relates to the widely used [`pytest-xdist`](https://github.com/pytest-dev/pytest-xdist) plugin. `pytest-hexaqueue` is engineered to work **in place of** `pytest-xdist` for distributed multi-machine execution, or **alongside** `pytest-xdist` in a hybrid two-tier concurrency topology.
|
|
38
|
+
|
|
39
|
+
### 1. In Place of `pytest-xdist` (Multi-Node & Cloud-Native Execution)
|
|
40
|
+
|
|
41
|
+
`pytest-xdist` excels at local multi-core parallelism (`pytest -n auto`), spawning worker processes on a single physical host. While `xdist` supports remote execution over SSH (`--tx ssh=...`), setting it up across machines requires identical remote virtualenvs, manual SSH daemon maintenance, fragile socket streams, and offers no queueing, budget governance, or automated repo synchronization.
|
|
42
|
+
|
|
43
|
+
**`pytest-hexaqueue` replaces `pytest-xdist` when scaling tests beyond a single local machine:**
|
|
44
|
+
|
|
45
|
+
- **Content-Addressed Workspace Sync (CAS)**: Automatically snapshots uncommitted workspace diffs and git revisions into `hexaqueue-collateral` content-addressed storage (SHA-256), synchronizing code to worker nodes instantly without rsync or shared network mounts.
|
|
46
|
+
- **Multi-CSP Free-Tier Mesh**: Schedules test slices across free compute quotas (OCI 4-vCPU ARM, GCP e2-micro, AWS t4g.small, Azure B1s, and local worker pools) while mathematically enforcing a $0.00 spend ceiling.
|
|
47
|
+
- **Fair-Share Queueing & Preemption**: Leverages Hexaqueue's central scheduling engine with priority aging and placement constraints rather than naive socket round-robining.
|
|
48
|
+
- **Fail-Safe Fallback**: If remote cluster nodes are offline or unreachable, `--hq-fallback` (enabled by default) seamlessly falls back to standard in-process test execution.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Replace local xdist with 8 remote multi-CSP cloud slices
|
|
52
|
+
pytest --hexaqueue --hq-slices 8 --hq-free-tier
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### 2. Alongside `pytest-xdist` (Two-Tier Hybrid Concurrency)
|
|
58
|
+
|
|
59
|
+
`pytest-hexaqueue` and `pytest-xdist` operate at different structural layers of the concurrency model and can be combined to achieve maximal compute throughput:
|
|
60
|
+
|
|
61
|
+
- **Tier 1 (Macro-Level / Horizontal Partitioning)**: `pytest-hexaqueue` partitions the overall test suite into coarse-grained slices and dispatches them across distinct cloud instances and worker nodes.
|
|
62
|
+
- **Tier 2 (Micro-Level / Vertical Parallelism)**: Inside each remote worker node (or container), the test runner invokes `pytest -n auto`, distributing that node's assigned slice across all local vCPUs.
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
graph TD
|
|
66
|
+
subgraph S1["Developer Workstation / CI Host (Tier 1: Macro-Distribution)"]
|
|
67
|
+
DEV["Developer runs: pytest --hexaqueue --hq-slices 3"]
|
|
68
|
+
PLUGIN["pytest-hexaqueue (Slicing Engine)"]
|
|
69
|
+
DEV --> PLUGIN
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
subgraph S2["Hexaqueue Controller & Mesh"]
|
|
73
|
+
HQ["Hexaqueue Server (Free-Tier Mesh Router)"]
|
|
74
|
+
PLUGIN -->|Dispatch Slices| HQ
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
subgraph S3["Remote Worker 1 (OCI 4-vCPU ARM)"]
|
|
78
|
+
W1["Worker 1 (Slice 0: 100 tests)"]
|
|
79
|
+
X1["pytest-xdist (-n 4)"]
|
|
80
|
+
W1 --> X1
|
|
81
|
+
X1 --> C1["Worker Core 1"]
|
|
82
|
+
X1 --> C2["Worker Core 2"]
|
|
83
|
+
X1 --> C3["Worker Core 3"]
|
|
84
|
+
X1 --> C4["Worker Core 4"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
subgraph S4["Remote Worker 2 (GCP e2-micro)"]
|
|
88
|
+
W2["Worker 2 (Slice 1: 50 tests)"]
|
|
89
|
+
X2["pytest-xdist (-n 2)"]
|
|
90
|
+
W2 --> X2
|
|
91
|
+
X2 --> C5["Worker Core 1"]
|
|
92
|
+
X2 --> C6["Worker Core 2"]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
subgraph S5["Remote Worker 3 (Local On-Prem Node)"]
|
|
96
|
+
W3["Worker 3 (Slice 2: 100 tests)"]
|
|
97
|
+
X3["pytest-xdist (-n 8)"]
|
|
98
|
+
W3 --> X3
|
|
99
|
+
X3 --> C7["Worker Cores (1..8)"]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
HQ -->|Slice 0| W1
|
|
103
|
+
HQ -->|Slice 1| W2
|
|
104
|
+
HQ -->|Slice 2| W3
|
|
105
|
+
|
|
106
|
+
W1 -.->|Outcomes & Traces| PLUGIN
|
|
107
|
+
W2 -.->|Outcomes & Traces| PLUGIN
|
|
108
|
+
W3 -.->|Outcomes & Traces| PLUGIN
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### 3. Feature Comparison Matrix
|
|
114
|
+
|
|
115
|
+
| Capability | `pytest-xdist` | `pytest-hexaqueue` | Combined (Hybrid) |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| **Primary Parallelism Domain** | Single-host multi-process (`-n N`) | Multi-node, multi-cloud cluster (`--hq-slices N`) | Multi-node slices + multi-core execution per node |
|
|
118
|
+
| **Compute Topology** | Local machine cores (SSH mode manual/fragile) | Federated multi-CSP mesh (OCI, GCP, AWS, Azure, Local) | Cloud cluster nodes saturated with local worker processes |
|
|
119
|
+
| **Workspace & Code Sync** | Requires identical filesystem / mount | Content-addressed SHA-256 CAS delta sync | CAS sync to host, executed across cores |
|
|
120
|
+
| **Budget & Cost Safety** | Unaware of cloud costs or quotas | Enforced $0.00 Free-Tier ceiling via Governor | Free compute pooled with local multi-core utilization |
|
|
121
|
+
| **Scheduling Intelligence** | Local round-robin / load-balancing | Fair-share, priority aging, compute placement affinity | Cluster scheduling + local worker core distribution |
|
|
122
|
+
| **Node Failure Resilience** | Crashed worker fails entire test run | Automatic rescheduling or local fallback | Node failure quarantined away from developer machine |
|
|
123
|
+
| **Terminal Reporting** | Aggregates local worker subprocesses | Live streaming telemetry + multi-CSP node annotations | Unified terminal summary with remote node origins |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 🚀 Quickstart & Usage
|
|
128
|
+
|
|
129
|
+
### 1. Installation
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv add --dev pytest-hexaqueue
|
|
133
|
+
# or
|
|
134
|
+
pip install pytest-hexaqueue
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 2. Standalone Distributed Run
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Uses project config in pyproject.toml / hexaqueue.yaml
|
|
141
|
+
pytest --hexaqueue
|
|
142
|
+
|
|
143
|
+
# Activate a named profile (automatically enables Hexaqueue execution)
|
|
144
|
+
pytest --profile ci
|
|
145
|
+
pytest --profile container
|
|
146
|
+
pytest --profile free-tier
|
|
147
|
+
|
|
148
|
+
# Or using the canonical --hq-profile alias:
|
|
149
|
+
pytest --hq-profile ci
|
|
150
|
+
|
|
151
|
+
# Explicit one-off overrides
|
|
152
|
+
pytest --hexaqueue --hq-slices 8
|
|
153
|
+
pytest --hexaqueue --hq-config path/to/custom.yaml
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 3. Hybrid Two-Tier Execution (Alongside xdist)
|
|
157
|
+
|
|
158
|
+
In your remote worker execution template or container runner script, configure the runner to consume its assigned slice with `pytest-xdist`:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Inside the Hexaqueue worker container executing an assigned slice:
|
|
162
|
+
pytest $(cat /tmp/hexaqueue/slice_items.txt) -n auto
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## ⚙️ Configuration & Options Reference
|
|
168
|
+
|
|
169
|
+
### Configuration via `pyproject.toml` or `hexaqueue.yaml`
|
|
170
|
+
|
|
171
|
+
Instead of passing CLI flags, configure project defaults and named profiles in `pyproject.toml`:
|
|
172
|
+
|
|
173
|
+
```toml
|
|
174
|
+
[tool.hexaqueue]
|
|
175
|
+
server_url = "http://127.0.0.1:8000"
|
|
176
|
+
slices = 4
|
|
177
|
+
slicer = "round-robin"
|
|
178
|
+
fallback = true
|
|
179
|
+
mode = "subprocess"
|
|
180
|
+
|
|
181
|
+
[tool.hexaqueue.profiles.container]
|
|
182
|
+
mode = "container"
|
|
183
|
+
slices = 4
|
|
184
|
+
container = { image = "hexaqueue-worker:latest", runtime = "auto" }
|
|
185
|
+
|
|
186
|
+
[tool.hexaqueue.profiles.ci]
|
|
187
|
+
server_url = "https://hq-cluster.corp.internal"
|
|
188
|
+
slices = 16
|
|
189
|
+
fallback = false
|
|
190
|
+
|
|
191
|
+
[tool.hexaqueue.profiles.free-tier]
|
|
192
|
+
free_tier = true
|
|
193
|
+
slices = 4
|
|
194
|
+
pool = "arm64-always-free"
|
|
195
|
+
|
|
196
|
+
[tool.hexaqueue.profiles.paid-cloud]
|
|
197
|
+
free_tier = false
|
|
198
|
+
slices = 8
|
|
199
|
+
mode = "csp"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### CLI Overrides Reference
|
|
203
|
+
|
|
204
|
+
| Option | Environment Variable | Default | Description |
|
|
205
|
+
|---|---|---|---|
|
|
206
|
+
| `--hexaqueue` | `HEXAQUEUE_PYTEST` | `False` | Enable Hexaqueue distributed test execution |
|
|
207
|
+
| `--hq-config` | `HEXAQUEUE_CONFIG` | None | Path to configuration file (`pyproject.toml` or `hexaqueue.yaml`) |
|
|
208
|
+
| `--hq-profile` | `HEXAQUEUE_PROFILE` | None | Active configuration profile name (e.g., `ci`, `container`, `paid-cloud`) |
|
|
209
|
+
| `--hq-server` | `HEXAQUEUE_SERVER_URL` | Config / `http://127.0.0.1:8000` | Hexaqueue cluster controller base URL |
|
|
210
|
+
| `--hq-token` | `HEXAQUEUE_API_TOKEN` | None | Authentication bearer token |
|
|
211
|
+
| `--hq-slices` | `HEXAQUEUE_SLICES` | Config / `4` | Number of parallel execution slices |
|
|
212
|
+
| `--hq-container` | `HEXAQUEUE_CONTAINER` | Config / `False` | Execute slices inside isolated OCI container sandboxes |
|
|
213
|
+
| `--hq-image` | `HEXAQUEUE_WORKER_IMAGE` | Config / `latest` | Container image tag for worker runtime |
|
|
214
|
+
| `--hq-pool` | `HEXAQUEUE_POOL` | Config / `default` | Target compute instance pool for runner placement |
|
|
215
|
+
| `--hq-paid` | `HEXAQUEUE_PAID` | Config / `False` | Authorize paid cloud compute (disables zero-cost free-tier guardrails) |
|
|
216
|
+
| `--hq-free-tier` | `HEXAQUEUE_FREE_TIER` | Config / `True` | Enforce zero-cost Multi-CSP Free-Tier mesh execution ($0.00 spend ceiling) |
|
|
217
|
+
| `--hq-fallback` | `HEXAQUEUE_LOCAL_FALLBACK` | Config / `True` | Fall back to local in-process execution if cluster is unreachable |
|
|
218
|
+
| `--hq-slicer` | `HEXAQUEUE_SLICER` | Config / `round-robin` | Test partitioning algorithm: `round-robin` or `file` |
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# 🧪 `pytest-hexaqueue`
|
|
2
|
+
|
|
3
|
+
> Distributed Test Execution, Slicing Engine, and Multi-CSP Free-Tier Bursting Plugin for Pytest.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pytest-hexaqueue/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://dopplereffect.us/hexaqueue/)
|
|
8
|
+
[](https://dopplereffect.us/hexastack/)
|
|
9
|
+
[](https://dopplereffect.us/hexaqual/)
|
|
10
|
+
[](../../LICENSE)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🎯 Overview
|
|
15
|
+
|
|
16
|
+
`pytest-hexaqueue` is a first-class distributed test execution plugin for `pytest`. It partitions large test suites into balanced execution slices and fans out execution across **Hexaqueue cluster workers** and federated **Multi-CSP Free-Tier Meshes** (OCI, GCP, AWS, Azure, and local nodes), achieving horizontal test scalability with zero cloud spend ($0.00 spend ceiling).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ⚖️ Coexistence & Comparison: `pytest-hexaqueue` vs. `pytest-xdist`
|
|
21
|
+
|
|
22
|
+
A common question is how `pytest-hexaqueue` relates to the widely used [`pytest-xdist`](https://github.com/pytest-dev/pytest-xdist) plugin. `pytest-hexaqueue` is engineered to work **in place of** `pytest-xdist` for distributed multi-machine execution, or **alongside** `pytest-xdist` in a hybrid two-tier concurrency topology.
|
|
23
|
+
|
|
24
|
+
### 1. In Place of `pytest-xdist` (Multi-Node & Cloud-Native Execution)
|
|
25
|
+
|
|
26
|
+
`pytest-xdist` excels at local multi-core parallelism (`pytest -n auto`), spawning worker processes on a single physical host. While `xdist` supports remote execution over SSH (`--tx ssh=...`), setting it up across machines requires identical remote virtualenvs, manual SSH daemon maintenance, fragile socket streams, and offers no queueing, budget governance, or automated repo synchronization.
|
|
27
|
+
|
|
28
|
+
**`pytest-hexaqueue` replaces `pytest-xdist` when scaling tests beyond a single local machine:**
|
|
29
|
+
|
|
30
|
+
- **Content-Addressed Workspace Sync (CAS)**: Automatically snapshots uncommitted workspace diffs and git revisions into `hexaqueue-collateral` content-addressed storage (SHA-256), synchronizing code to worker nodes instantly without rsync or shared network mounts.
|
|
31
|
+
- **Multi-CSP Free-Tier Mesh**: Schedules test slices across free compute quotas (OCI 4-vCPU ARM, GCP e2-micro, AWS t4g.small, Azure B1s, and local worker pools) while mathematically enforcing a $0.00 spend ceiling.
|
|
32
|
+
- **Fair-Share Queueing & Preemption**: Leverages Hexaqueue's central scheduling engine with priority aging and placement constraints rather than naive socket round-robining.
|
|
33
|
+
- **Fail-Safe Fallback**: If remote cluster nodes are offline or unreachable, `--hq-fallback` (enabled by default) seamlessly falls back to standard in-process test execution.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Replace local xdist with 8 remote multi-CSP cloud slices
|
|
37
|
+
pytest --hexaqueue --hq-slices 8 --hq-free-tier
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### 2. Alongside `pytest-xdist` (Two-Tier Hybrid Concurrency)
|
|
43
|
+
|
|
44
|
+
`pytest-hexaqueue` and `pytest-xdist` operate at different structural layers of the concurrency model and can be combined to achieve maximal compute throughput:
|
|
45
|
+
|
|
46
|
+
- **Tier 1 (Macro-Level / Horizontal Partitioning)**: `pytest-hexaqueue` partitions the overall test suite into coarse-grained slices and dispatches them across distinct cloud instances and worker nodes.
|
|
47
|
+
- **Tier 2 (Micro-Level / Vertical Parallelism)**: Inside each remote worker node (or container), the test runner invokes `pytest -n auto`, distributing that node's assigned slice across all local vCPUs.
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
graph TD
|
|
51
|
+
subgraph S1["Developer Workstation / CI Host (Tier 1: Macro-Distribution)"]
|
|
52
|
+
DEV["Developer runs: pytest --hexaqueue --hq-slices 3"]
|
|
53
|
+
PLUGIN["pytest-hexaqueue (Slicing Engine)"]
|
|
54
|
+
DEV --> PLUGIN
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph S2["Hexaqueue Controller & Mesh"]
|
|
58
|
+
HQ["Hexaqueue Server (Free-Tier Mesh Router)"]
|
|
59
|
+
PLUGIN -->|Dispatch Slices| HQ
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
subgraph S3["Remote Worker 1 (OCI 4-vCPU ARM)"]
|
|
63
|
+
W1["Worker 1 (Slice 0: 100 tests)"]
|
|
64
|
+
X1["pytest-xdist (-n 4)"]
|
|
65
|
+
W1 --> X1
|
|
66
|
+
X1 --> C1["Worker Core 1"]
|
|
67
|
+
X1 --> C2["Worker Core 2"]
|
|
68
|
+
X1 --> C3["Worker Core 3"]
|
|
69
|
+
X1 --> C4["Worker Core 4"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
subgraph S4["Remote Worker 2 (GCP e2-micro)"]
|
|
73
|
+
W2["Worker 2 (Slice 1: 50 tests)"]
|
|
74
|
+
X2["pytest-xdist (-n 2)"]
|
|
75
|
+
W2 --> X2
|
|
76
|
+
X2 --> C5["Worker Core 1"]
|
|
77
|
+
X2 --> C6["Worker Core 2"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
subgraph S5["Remote Worker 3 (Local On-Prem Node)"]
|
|
81
|
+
W3["Worker 3 (Slice 2: 100 tests)"]
|
|
82
|
+
X3["pytest-xdist (-n 8)"]
|
|
83
|
+
W3 --> X3
|
|
84
|
+
X3 --> C7["Worker Cores (1..8)"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
HQ -->|Slice 0| W1
|
|
88
|
+
HQ -->|Slice 1| W2
|
|
89
|
+
HQ -->|Slice 2| W3
|
|
90
|
+
|
|
91
|
+
W1 -.->|Outcomes & Traces| PLUGIN
|
|
92
|
+
W2 -.->|Outcomes & Traces| PLUGIN
|
|
93
|
+
W3 -.->|Outcomes & Traces| PLUGIN
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 3. Feature Comparison Matrix
|
|
99
|
+
|
|
100
|
+
| Capability | `pytest-xdist` | `pytest-hexaqueue` | Combined (Hybrid) |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| **Primary Parallelism Domain** | Single-host multi-process (`-n N`) | Multi-node, multi-cloud cluster (`--hq-slices N`) | Multi-node slices + multi-core execution per node |
|
|
103
|
+
| **Compute Topology** | Local machine cores (SSH mode manual/fragile) | Federated multi-CSP mesh (OCI, GCP, AWS, Azure, Local) | Cloud cluster nodes saturated with local worker processes |
|
|
104
|
+
| **Workspace & Code Sync** | Requires identical filesystem / mount | Content-addressed SHA-256 CAS delta sync | CAS sync to host, executed across cores |
|
|
105
|
+
| **Budget & Cost Safety** | Unaware of cloud costs or quotas | Enforced $0.00 Free-Tier ceiling via Governor | Free compute pooled with local multi-core utilization |
|
|
106
|
+
| **Scheduling Intelligence** | Local round-robin / load-balancing | Fair-share, priority aging, compute placement affinity | Cluster scheduling + local worker core distribution |
|
|
107
|
+
| **Node Failure Resilience** | Crashed worker fails entire test run | Automatic rescheduling or local fallback | Node failure quarantined away from developer machine |
|
|
108
|
+
| **Terminal Reporting** | Aggregates local worker subprocesses | Live streaming telemetry + multi-CSP node annotations | Unified terminal summary with remote node origins |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 🚀 Quickstart & Usage
|
|
113
|
+
|
|
114
|
+
### 1. Installation
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uv add --dev pytest-hexaqueue
|
|
118
|
+
# or
|
|
119
|
+
pip install pytest-hexaqueue
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2. Standalone Distributed Run
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Uses project config in pyproject.toml / hexaqueue.yaml
|
|
126
|
+
pytest --hexaqueue
|
|
127
|
+
|
|
128
|
+
# Activate a named profile (automatically enables Hexaqueue execution)
|
|
129
|
+
pytest --profile ci
|
|
130
|
+
pytest --profile container
|
|
131
|
+
pytest --profile free-tier
|
|
132
|
+
|
|
133
|
+
# Or using the canonical --hq-profile alias:
|
|
134
|
+
pytest --hq-profile ci
|
|
135
|
+
|
|
136
|
+
# Explicit one-off overrides
|
|
137
|
+
pytest --hexaqueue --hq-slices 8
|
|
138
|
+
pytest --hexaqueue --hq-config path/to/custom.yaml
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. Hybrid Two-Tier Execution (Alongside xdist)
|
|
142
|
+
|
|
143
|
+
In your remote worker execution template or container runner script, configure the runner to consume its assigned slice with `pytest-xdist`:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Inside the Hexaqueue worker container executing an assigned slice:
|
|
147
|
+
pytest $(cat /tmp/hexaqueue/slice_items.txt) -n auto
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## ⚙️ Configuration & Options Reference
|
|
153
|
+
|
|
154
|
+
### Configuration via `pyproject.toml` or `hexaqueue.yaml`
|
|
155
|
+
|
|
156
|
+
Instead of passing CLI flags, configure project defaults and named profiles in `pyproject.toml`:
|
|
157
|
+
|
|
158
|
+
```toml
|
|
159
|
+
[tool.hexaqueue]
|
|
160
|
+
server_url = "http://127.0.0.1:8000"
|
|
161
|
+
slices = 4
|
|
162
|
+
slicer = "round-robin"
|
|
163
|
+
fallback = true
|
|
164
|
+
mode = "subprocess"
|
|
165
|
+
|
|
166
|
+
[tool.hexaqueue.profiles.container]
|
|
167
|
+
mode = "container"
|
|
168
|
+
slices = 4
|
|
169
|
+
container = { image = "hexaqueue-worker:latest", runtime = "auto" }
|
|
170
|
+
|
|
171
|
+
[tool.hexaqueue.profiles.ci]
|
|
172
|
+
server_url = "https://hq-cluster.corp.internal"
|
|
173
|
+
slices = 16
|
|
174
|
+
fallback = false
|
|
175
|
+
|
|
176
|
+
[tool.hexaqueue.profiles.free-tier]
|
|
177
|
+
free_tier = true
|
|
178
|
+
slices = 4
|
|
179
|
+
pool = "arm64-always-free"
|
|
180
|
+
|
|
181
|
+
[tool.hexaqueue.profiles.paid-cloud]
|
|
182
|
+
free_tier = false
|
|
183
|
+
slices = 8
|
|
184
|
+
mode = "csp"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### CLI Overrides Reference
|
|
188
|
+
|
|
189
|
+
| Option | Environment Variable | Default | Description |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| `--hexaqueue` | `HEXAQUEUE_PYTEST` | `False` | Enable Hexaqueue distributed test execution |
|
|
192
|
+
| `--hq-config` | `HEXAQUEUE_CONFIG` | None | Path to configuration file (`pyproject.toml` or `hexaqueue.yaml`) |
|
|
193
|
+
| `--hq-profile` | `HEXAQUEUE_PROFILE` | None | Active configuration profile name (e.g., `ci`, `container`, `paid-cloud`) |
|
|
194
|
+
| `--hq-server` | `HEXAQUEUE_SERVER_URL` | Config / `http://127.0.0.1:8000` | Hexaqueue cluster controller base URL |
|
|
195
|
+
| `--hq-token` | `HEXAQUEUE_API_TOKEN` | None | Authentication bearer token |
|
|
196
|
+
| `--hq-slices` | `HEXAQUEUE_SLICES` | Config / `4` | Number of parallel execution slices |
|
|
197
|
+
| `--hq-container` | `HEXAQUEUE_CONTAINER` | Config / `False` | Execute slices inside isolated OCI container sandboxes |
|
|
198
|
+
| `--hq-image` | `HEXAQUEUE_WORKER_IMAGE` | Config / `latest` | Container image tag for worker runtime |
|
|
199
|
+
| `--hq-pool` | `HEXAQUEUE_POOL` | Config / `default` | Target compute instance pool for runner placement |
|
|
200
|
+
| `--hq-paid` | `HEXAQUEUE_PAID` | Config / `False` | Authorize paid cloud compute (disables zero-cost free-tier guardrails) |
|
|
201
|
+
| `--hq-free-tier` | `HEXAQUEUE_FREE_TIER` | Config / `True` | Enforce zero-cost Multi-CSP Free-Tier mesh execution ($0.00 spend ceiling) |
|
|
202
|
+
| `--hq-fallback` | `HEXAQUEUE_LOCAL_FALLBACK` | Config / `True` | Fall back to local in-process execution if cluster is unreachable |
|
|
203
|
+
| `--hq-slicer` | `HEXAQUEUE_SLICER` | Config / `round-robin` | Test partitioning algorithm: `round-robin` or `file` |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pytest-hexaqueue"
|
|
3
|
+
version = "0.6.0"
|
|
4
|
+
description = "pytest-hexaqueue - Distributed Test Slicing, Remote Execution, and Multi-CSP Free-Tier Runner Plugin"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"hexaqueue-collateral",
|
|
10
|
+
"hexaqueue-core",
|
|
11
|
+
"httpx>=0.27.0",
|
|
12
|
+
"pydantic>=2.10.0",
|
|
13
|
+
"pytest>=8.3.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[[project.authors]]
|
|
17
|
+
name = "Richard West"
|
|
18
|
+
email = "dopplereffect.us@gmail.com"
|
|
19
|
+
|
|
20
|
+
[project.entry-points.pytest11]
|
|
21
|
+
hexaqueue = "pytest_hexaqueue.infra.plugin"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["uv_build>=0.12.3,<0.14.0"]
|
|
25
|
+
build-backend = "uv_build"
|
|
26
|
+
|
|
27
|
+
[tool.uv.sources.hexaqueue-collateral]
|
|
28
|
+
workspace = true
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources.hexaqueue-core]
|
|
31
|
+
workspace = true
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pytest-hexaqueue"
|
|
3
|
+
version = "0.6.0"
|
|
4
|
+
description = "pytest-hexaqueue - Distributed Test Slicing, Remote Execution, and Multi-CSP Free-Tier Runner Plugin"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Richard West", email = "dopplereffect.us@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"hexaqueue-collateral",
|
|
13
|
+
"hexaqueue-core",
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
"pydantic>=2.10.0",
|
|
16
|
+
"pytest>=8.3.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.entry-points.pytest11]
|
|
20
|
+
hexaqueue = "pytest_hexaqueue.infra.plugin"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.12.3,<0.14.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[tool.uv.sources]
|
|
27
|
+
hexaqueue-collateral = { workspace = true }
|
|
28
|
+
hexaqueue-core = { workspace = true }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""pytest-hexaqueue - Distributed Test Slicing, Remote Execution, and Multi-CSP Free-Tier Runner Plugin."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"adapters",
|
|
8
|
+
"domain",
|
|
9
|
+
"FileContiguousTestSlicer",
|
|
10
|
+
"HexaqueueTestDispatchClient",
|
|
11
|
+
"HexaqueueTestDispatchPort",
|
|
12
|
+
"infra",
|
|
13
|
+
"InMemoryTestDispatchClient",
|
|
14
|
+
"ports",
|
|
15
|
+
"RoundRobinTestSlicer",
|
|
16
|
+
"SliceOutcomeReport",
|
|
17
|
+
"TestItemInfo",
|
|
18
|
+
"TestOutcomeItem",
|
|
19
|
+
"TestRunSession",
|
|
20
|
+
"TestSlicerPort",
|
|
21
|
+
"TestSliceSpec",
|
|
22
|
+
"WorkspaceDeltaBundle",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
_EXPORTS: dict[str, tuple[str, str]] = {
|
|
26
|
+
"FileContiguousTestSlicer": (
|
|
27
|
+
"pytest_hexaqueue.adapters.slicer",
|
|
28
|
+
"FileContiguousTestSlicer",
|
|
29
|
+
),
|
|
30
|
+
"HexaqueueTestDispatchClient": (
|
|
31
|
+
"pytest_hexaqueue.adapters.client",
|
|
32
|
+
"HexaqueueTestDispatchClient",
|
|
33
|
+
),
|
|
34
|
+
"HexaqueueTestDispatchPort": (
|
|
35
|
+
"pytest_hexaqueue.ports.client",
|
|
36
|
+
"HexaqueueTestDispatchPort",
|
|
37
|
+
),
|
|
38
|
+
"InMemoryTestDispatchClient": (
|
|
39
|
+
"pytest_hexaqueue.adapters.client",
|
|
40
|
+
"InMemoryTestDispatchClient",
|
|
41
|
+
),
|
|
42
|
+
"RoundRobinTestSlicer": (
|
|
43
|
+
"pytest_hexaqueue.adapters.slicer",
|
|
44
|
+
"RoundRobinTestSlicer",
|
|
45
|
+
),
|
|
46
|
+
"SliceOutcomeReport": ("pytest_hexaqueue.domain.outcome", "SliceOutcomeReport"),
|
|
47
|
+
"TestItemInfo": ("pytest_hexaqueue.domain.slice", "TestItemInfo"),
|
|
48
|
+
"TestOutcomeItem": ("pytest_hexaqueue.domain.outcome", "TestOutcomeItem"),
|
|
49
|
+
"TestRunSession": ("pytest_hexaqueue.domain.slice", "TestRunSession"),
|
|
50
|
+
"TestSlicerPort": ("pytest_hexaqueue.ports.slicer", "TestSlicerPort"),
|
|
51
|
+
"TestSliceSpec": ("pytest_hexaqueue.domain.slice", "TestSliceSpec"),
|
|
52
|
+
"WorkspaceDeltaBundle": ("pytest_hexaqueue.domain.delta", "WorkspaceDeltaBundle"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def __getattr__(name: str) -> Any:
|
|
57
|
+
"""Lazily load symbols upon attribute access."""
|
|
58
|
+
if name in _EXPORTS:
|
|
59
|
+
mod_name, attr_name = _EXPORTS[name]
|
|
60
|
+
mod = importlib.import_module(mod_name)
|
|
61
|
+
val = getattr(mod, attr_name)
|
|
62
|
+
globals()[name] = val
|
|
63
|
+
return val
|
|
64
|
+
if name in ("adapters", "domain", "infra", "ports"):
|
|
65
|
+
mod = importlib.import_module(f"pytest_hexaqueue.{name}")
|
|
66
|
+
globals()[name] = mod
|
|
67
|
+
return mod
|
|
68
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
69
|
+
raise AttributeError(msg)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def __dir__() -> list[str]:
|
|
73
|
+
"""Return public export list including lazy symbols."""
|
|
74
|
+
return sorted(set(list(globals().keys()) + __all__))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Adapter implementations for pytest-hexaqueue."""
|
|
2
|
+
|
|
3
|
+
from pytest_hexaqueue.adapters.client import (
|
|
4
|
+
HexaqueueTestDispatchClient,
|
|
5
|
+
InMemoryTestDispatchClient,
|
|
6
|
+
)
|
|
7
|
+
from pytest_hexaqueue.adapters.slicer import (
|
|
8
|
+
FileContiguousTestSlicer,
|
|
9
|
+
RoundRobinTestSlicer,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"FileContiguousTestSlicer",
|
|
14
|
+
"HexaqueueTestDispatchClient",
|
|
15
|
+
"InMemoryTestDispatchClient",
|
|
16
|
+
"RoundRobinTestSlicer",
|
|
17
|
+
]
|