Skip to content

Commit 4ff564c

Browse files
authored
Merge pull request #3297 from IntersectMBO/add_instructions_for_ai
docs: add AI agent development guide
2 parents 4abb711 + 2d7cb34 commit 4ff564c

2 files changed

Lines changed: 372 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# AGENTS.md - AI Agent Development Guide
2+
3+
This guide provides instructions for AI agents working with the cardano-node-tests repository, assuming the development environment is already activated.
4+
5+
---
6+
7+
## 🧪 Running Individual Tests
8+
9+
When the development environment is activated, you can run individual tests using pytest.
10+
11+
### Basic Test Execution
12+
13+
Run a specific test by name:
14+
15+
```sh
16+
pytest -k "test_minting_one_token" cardano_node_tests/tests/tests_plutus
17+
```
18+
19+
Run multiple tests matching a pattern:
20+
21+
```sh
22+
pytest -k "test_missing_tx_out or test_multiple_same_txins" cardano_node_tests
23+
```
24+
25+
Run tests with a specific marker:
26+
27+
```sh
28+
pytest -m smoke cardano_node_tests/tests/test_cli.py
29+
```
30+
31+
### Debugging and Verbose Output
32+
33+
Enable full CLI command logging:
34+
35+
```sh
36+
pytest -s --log-level=debug -k test_minting_one_token cardano_node_tests/tests/tests_plutus
37+
```
38+
39+
### Test Organization
40+
41+
Tests are organized under:
42+
43+
- `cardano_node_tests/tests/` - Main test directory
44+
- `cardano_node_tests/tests/tests_plutus/` - Plutus-specific tests for all Plutus versions
45+
- `cardano_node_tests/tests/tests_plutus_v2/` - Plutus-specific tests for PlutusV2
46+
- `cardano_node_tests/tests/tests_plutus_v3/` - Plutus-specific tests for PlutusV3
47+
- `cardano_node_tests/tests/tests_conway/` - Conway era specific tests
48+
- `cardano_node_tests/tests/data/` - Test data files
49+
50+
### Framework files
51+
52+
- `cardano_node_tests/cluster_management/` - Cluster management utilities for managing local testnet clusters
53+
- `cardano_node_tests/utils/` - Utility functions for tests and the framework
54+
- `cardano_node_tests/pytest_plugins/` - Pytest plugins, namely a custom pytest-xdist scheduler
55+
56+
---
57+
58+
## 🔧 Making Code Changes
59+
60+
### Coding Guidelines
61+
62+
1. **Follow the Google Python Style Guide**
63+
- See: <https://google.github.io/styleguide/pyguide.html>
64+
65+
2. **Use Type Hints**
66+
- All functions and methods should have type hints for parameters and return types
67+
- Test functions and methods should also include type hints, with the exception that return type `None` is omitted
68+
69+
3. **Use Ruff for Formatting**
70+
- Formatting is enforced via pre-commit hooks
71+
- Ruff handles both linting and formatting
72+
73+
4. **Run Linters**
74+
75+
```sh
76+
make lint
77+
```
78+
79+
### Making Changes to Dependencies
80+
81+
If you modify dependencies in `pyproject.toml`, update the lockfile:
82+
83+
```sh
84+
make update-lockfile
85+
```
86+
87+
---
88+
89+
## 📝 Testing Workflow
90+
91+
1. **Make your code changes** following the Google Python Style Guide
92+
93+
2. **Run relevant tests** to verify your changes:
94+
95+
```sh
96+
pytest -k "your_test_pattern" cardano_node_tests
97+
```
98+
99+
3. **Run linters** to ensure code quality:
100+
101+
```sh
102+
make lint
103+
```
104+
105+
## Changing Markdown and ReStructuredText Documentation
106+
107+
1. **Make your changes**
108+
109+
2. **Run Linters to check formatting**:
110+
111+
```sh
112+
make lint
113+
```
114+
115+
---
116+
117+
## 🎯 Common Test Patterns
118+
119+
### Running Tests by Category
120+
121+
By marker:
122+
123+
```sh
124+
pytest -m smoke cardano_node_tests
125+
```
126+
127+
By test path:
128+
129+
```sh
130+
pytest cardano_node_tests/tests/test_cli.py
131+
```
132+
133+
Framework tests:
134+
135+
```sh
136+
pytest framework_tests/
137+
```
138+
139+
### Running Tests with Custom Configuration
140+
141+
Filter by multiple conditions:
142+
143+
```sh
144+
pytest -k "test_stake_pool_low_cost or test_reward_amount" cardano_node_tests
145+
```
146+
147+
With marker exclusion:
148+
149+
```sh
150+
pytest -m "not long" cardano_node_tests
151+
```
152+
153+
---
154+
155+
## 🔍 Verifying Your Environment
156+
157+
Before running tests, verify:
158+
159+
1. **Local testnet is running** - Check with `[ -S "$CARDANO_NODE_SOCKET_PATH" ] && echo RUNNING || echo NOT RUNNING`
160+
2. **Python Virtual environment is activated** - Check with `[ -n "$VIRTUAL_ENV" ] && echo ACTIVE || echo NOT ACTIVE`
161+
3. **Needed binaries are available** - Check that `cardano-node` and `cardano-cli` are in your PATH
162+
163+
---
164+
165+
## 🔐 Cluster Management and Resource Locking
166+
167+
The test framework uses a sophisticated cluster management system that allows multiple tests to run in parallel on shared cluster instances while coordinating access to shared resources.
168+
169+
### How Cluster Management Works
170+
171+
**Pool of Cluster Instances**: The framework can run multiple cluster instances concurrently (configured via `CLUSTERS_COUNT`). Each test worker requests a cluster instance to run a test on.
172+
173+
**Coordination via File System**: Workers communicate through status files created in a shared temporary directory. These files act as locks and signals indicating:
174+
175+
- Which test is running on which cluster instance
176+
- Which resources are locked or in use
177+
- When a cluster needs to be respun (restarted to clean state)
178+
179+
**Resource Management**: Tests declare what resources they need. The `ClusterManager` ensures only one test uses exclusive resources at a time.
180+
181+
**Cluster Respin**: Some tests modify cluster state irreversibly. These tests can request a "respin" to re-initialize the cluster to a clean state.
182+
183+
### Available Resources
184+
185+
Resources that are defined in `cluster_management.Resources`:
186+
187+
- **`CLUSTER`** - Whole cluster instance (used by every test, can be locked for exclusive access)
188+
- **`POOL1`, `POOL2`, `POOL3`, `ALL_POOLS`** - Individual stake pools or tuple of all pools
189+
- **`POOL_FOR_OFFLINE`** - Reserved pool for tests where pool stops producing blocks
190+
- **`PLUTUS`** - Plutus script resources
191+
- **`RESERVES`** - Reserve pot
192+
- **`TREASURY`** - Treasury pot
193+
- **`REWARDS`** - Rewards pot
194+
- **`POTS`** - Tuple of all pots (RESERVES, TREASURY, REWARDS)
195+
- **`PERF`** - Performance testing resources
196+
- **`DREPS`** - DRep resources
197+
198+
Other shared resources can exist, like Plutus scripts, when the same script is used in multiple tests.
199+
200+
### Resource Locking vs. Using
201+
202+
**`lock_resources`**: Exclusive access. No other test can use these resources until the current test finishes. Use when a test will modify the resource state.
203+
204+
**`use_resources`**: Shared access. Multiple tests can use the same resource, but the framework tracks usage. Use when a test only reads or makes non-conflicting changes. Other tests cannot lock the resource while it's in use.
205+
206+
### Writing Tests with Resource Management
207+
208+
#### Basic Pattern - Using the `cluster` Fixture
209+
210+
```python
211+
def test_something(cluster: clusterlib.ClusterLib):
212+
# Basic usage - cluster automatically manages common resources
213+
# No explicit resource declaration needed for simple tests
214+
...
215+
```
216+
217+
#### Custom Fixtures with Resource Requirements
218+
219+
**Lock specific resources** (exclusive access):
220+
221+
```python
222+
@pytest.fixture
223+
def cluster_lock_pool_and_pots(
224+
cluster_manager: cluster_management.ClusterManager,
225+
) -> tuple[clusterlib.ClusterLib, str]:
226+
cluster_obj = cluster_manager.get(
227+
lock_resources=[
228+
*cluster_management.Resources.POTS,
229+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
230+
]
231+
)
232+
pool_name = cluster_manager.get_locked_resources(
233+
from_set=cluster_management.Resources.ALL_POOLS
234+
)[0]
235+
return cluster_obj, pool_name
236+
```
237+
238+
**Use resources** (shared access):
239+
240+
```python
241+
@pytest.fixture
242+
def cluster_use_pool_and_rewards(
243+
cluster_manager: cluster_management.ClusterManager,
244+
) -> tuple[clusterlib.ClusterLib, str]:
245+
cluster_obj = cluster_manager.get(
246+
use_resources=[
247+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
248+
cluster_management.Resources.REWARDS,
249+
]
250+
)
251+
pool_name = cluster_manager.get_used_resources(
252+
from_set=cluster_management.Resources.ALL_POOLS
253+
)[0]
254+
return cluster_obj, pool_name
255+
```
256+
257+
**Lock multiple pools** (e.g., for pool interaction tests):
258+
259+
```python
260+
@pytest.fixture
261+
def cluster_lock_two_pools(
262+
cluster_manager: cluster_management.ClusterManager,
263+
) -> tuple[clusterlib.ClusterLib, str, str]:
264+
cluster_obj = cluster_manager.get(
265+
lock_resources=[
266+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
267+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
268+
]
269+
)
270+
pool_names = cluster_manager.get_locked_resources(
271+
from_set=cluster_management.Resources.ALL_POOLS
272+
)
273+
return cluster_obj, pool_names[0], pool_names[1]
274+
```
275+
276+
**Lock shared Plutus script**:
277+
278+
```python
279+
@pytest.fixture
280+
def cluster_lock_stake_script(
281+
cluster_manager: cluster_management.ClusterManager,
282+
) -> clusterlib.ClusterLib:
283+
"""Make sure just one staking Plutus test run at a time.
284+
285+
Plutus script always has the same address. When one script is used in multiple
286+
tests that are running in parallel, the balances etc. don't add up.
287+
"""
288+
cluster_obj = cluster_manager.get(
289+
lock_resources=[helpers.checksum(plutus_common.STAKE_PLUTUS_V2)],
290+
use_resources=[cluster_management.Resources.PLUTUS],
291+
)
292+
return cluster_obj
293+
```
294+
295+
### The `OneOf` Filter
296+
297+
`resources_management.OneOf` selects one available resource from a set:
298+
299+
```python
300+
# Select any available pool
301+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS)
302+
303+
# Use OneOf multiple times to select different resources
304+
[
305+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
306+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
307+
]
308+
# This will select two DIFFERENT pools from ALL_POOLS
309+
```
310+
311+
The filter automatically handles:
312+
313+
- Resources already locked by other tests
314+
- Resources already selected by previous filters in the same request
315+
- Returning empty list if no resource is available (test will wait)
316+
317+
### Best Practices
318+
319+
1. **Use `lock_resources` when modifying state**: If your test changes pool rewards, treasury, or other shared state, lock those resources.
320+
321+
2. **Use `use_resources` for read-only or non-conflicting operations**: If you only query data or make isolated changes, use shared access.
322+
323+
3. **Be specific about resource needs**: Only lock/use resources you actually need to minimize test blocking.
324+
325+
4. **Request respin when needed**: If your test leaves cluster in unusable state:
326+
327+
```python
328+
cluster_manager.set_needs_respin()
329+
```
330+
331+
5. **Use custom fixtures**: Create reusable fixtures for common resource patterns rather than repeating `cluster_manager.get()` calls.
332+
333+
6. **Check resource usage**: Use `cluster_manager.get_locked_resources()` or `cluster_manager.get_used_resources()` to see what was allocated.
334+
335+
### Example: Real-World Test Pattern
336+
337+
```python
338+
@pytest.fixture
339+
def cluster_and_pool(
340+
cluster_manager: cluster_management.ClusterManager,
341+
) -> tuple[clusterlib.ClusterLib, str]:
342+
"""Get cluster instance and select one pool."""
343+
cluster_obj = cluster_manager.get(
344+
use_resources=[
345+
resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS),
346+
]
347+
)
348+
pool_name = cluster_manager.get_used_resources(
349+
from_set=cluster_management.Resources.ALL_POOLS
350+
)[0]
351+
return cluster_obj, pool_name
352+
353+
354+
def test_stake_pool_rewards(
355+
cluster_and_pool: tuple[clusterlib.ClusterLib, str],
356+
):
357+
"""Test staking rewards with proper resource management."""
358+
cluster, pool_name = cluster_and_pool
359+
360+
# Test implementation using the selected pool
361+
# Multiple tests can run concurrently, each using different pools
362+
...
363+
```
364+
365+
### Troubleshooting Resource Issues
366+
367+
**Test hangs indefinitely**: Check if all required resources are available. Use `SCHEDULING_LOG` environment variable to see cluster scheduling decisions, if available.
368+
369+
**"Manager is already initialized" error**: Don't use multiple `cluster` fixtures in the same test. Use a single custom fixture that requests all needed resources at once.

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Use AGENTS.md
2+
3+
@AGENTS.md

0 commit comments

Comments
 (0)