GenLayer Testing Suite
Project description
GenLayer Testing Suite
A pytest-based testing framework for GenLayer intelligent contracts. Built on top of genlayer-py.
pip install genlayer-test
Two Ways to Test
The testing suite provides two execution modes. Pick the one that fits your workflow:
| Direct Mode | Studio Mode | |
|---|---|---|
| How it works | Runs contract Python code directly in-memory | Deploys to GenLayer Studio, interacts via RPC |
| Speed | ~milliseconds per test | ~minutes per test |
| Prerequisites | Python >= 3.12 | Python >= 3.12 + GenLayer Studio (Docker) |
| Best for | Unit tests, rapid development, CI/CD | Integration tests, consensus validation, testnet |
| Mocking | Foundry-style cheatcodes (mock_web, mock_llm) |
Mock validators with transaction context |
Start with Direct Mode. It's faster, simpler, and doesn't require Docker. Use Studio Mode when you need full network behavior, multi-validator consensus, or testnet deployment.
Direct Mode
Run contracts directly in Python — no simulator, no Docker, no network. Tests execute in milliseconds.
Quick Start
def test_storage(direct_vm, direct_deploy):
# Deploy contract in-memory
storage = direct_deploy("contracts/Storage.py", "initial")
# Read state directly
assert storage.get_storage() == "initial"
# Write state directly
storage.update_storage("updated")
assert storage.get_storage() == "updated"
Run with pytest:
pytest tests/ -v
Fixtures
| Fixture | Description |
|---|---|
direct_vm |
VM context with cheatcodes |
direct_deploy |
Deploy contracts directly |
direct_alice, direct_bob, direct_charlie |
Test addresses |
direct_owner |
Default sender address |
direct_accounts |
List of 10 test addresses |
Cheatcodes
# Change sender
direct_vm.sender = alice
# Prank (temporary sender change)
with direct_vm.prank(bob):
contract.method() # Called as bob
# Snapshots (captures full state: storage, mocks, sender, validators)
snap_id = direct_vm.snapshot()
contract.modify_state()
direct_vm.revert(snap_id) # Full state restored
# Expect revert
with direct_vm.expect_revert("Insufficient balance"):
contract.transfer(bob, 1000000)
# Mock web/LLM (regex pattern matching)
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
# Test validator consensus logic
contract.update_price() # Runs leader_fn, captures validator
direct_vm.clear_mocks() # Swap mocks for validator
direct_vm.mock_llm(r".*", "different result")
assert direct_vm.run_validator() is False # Validator disagrees
# Strict mocks (detect unused mocks)
direct_vm.strict_mocks = True
# Pickling validation (catch production serialization issues)
direct_vm.check_pickling = True
Full Direct Mode Documentation — fixtures, cheatcodes, validator testing, limitations, and complete examples.
Studio Mode
Deploy contracts to a running GenLayer Studio instance and interact via RPC. This gives you full network behavior including multi-validator consensus.
Prerequisites
- Python >= 3.12
- GenLayer Studio running (Docker)
Quick Start
from gltest import get_contract_factory, get_default_account
from gltest.assertions import tx_execution_succeeded
factory = get_contract_factory("MyContract")
contract = factory.deploy()
# Read method — returns value directly
result = contract.get_value().call()
# Write method — returns transaction receipt
tx_receipt = contract.set_value(args=["new_value"]).transact()
assert tx_execution_succeeded(tx_receipt)
Run with the gltest CLI:
gltest # Run all tests
gltest tests/test_mycontract.py # Specific file
gltest --network studionet # Specific network
gltest --leader-only # Skip consensus (faster)
gltest -v # Verbose output
Configuration
Create a gltest.config.yaml in your project root:
networks:
default: localnet
localnet:
url: "http://127.0.0.1:4000/api"
leader_only: false
studionet:
# Pre-configured — accounts auto-generated
testnet_asimov:
accounts:
- "${ACCOUNT_PRIVATE_KEY_1}"
- "${ACCOUNT_PRIVATE_KEY_2}"
from: "${ACCOUNT_PRIVATE_KEY_1}"
paths:
contracts: "contracts"
artifacts: "artifacts"
environment: .env
Key options:
- Networks:
localnetandstudionetwork out of the box.testnet_asimovrequires account keys. - Paths: Where your contracts and artifacts live.
- Environment:
.envfile for private keys.
Override via CLI:
gltest --network testnet_asimov
gltest --contracts-dir custom/contracts/path
gltest --rpc-url http://custom:4000/api
gltest --chain-type localnet
Contract Deployment
from gltest import get_contract_factory, get_default_account
from gltest.assertions import tx_execution_succeeded
factory = get_contract_factory("Storage")
# deploy() returns the contract instance (recommended)
contract = factory.deploy(
args=["initial_value"],
account=get_default_account(),
consensus_max_rotations=3,
)
# deploy_contract_tx() returns only the receipt
receipt = factory.deploy_contract_tx(args=["initial_value"])
assert tx_execution_succeeded(receipt)
Read and Write Methods
# Read — call() returns the value
result = contract.get_storage().call()
# Write — transact() returns a receipt
tx_receipt = contract.update_storage(args=["new_value"]).transact(
value=0,
consensus_max_rotations=3,
wait_interval=1000,
wait_retries=10,
)
assert tx_execution_succeeded(tx_receipt)
Assertions
from gltest.assertions import tx_execution_succeeded, tx_execution_failed
assert tx_execution_succeeded(tx_receipt)
assert tx_execution_failed(tx_receipt)
# Regex matching on stdout/stderr (localnet/studionet only)
assert tx_execution_succeeded(tx_receipt, match_std_out=r".*code \d+")
assert tx_execution_failed(tx_receipt, match_std_err=r"Method.*failed")
Fixtures
| Fixture | Scope | Description |
|---|---|---|
gl_client |
session | GenLayer client for network operations |
default_account |
session | Default account for transactions |
accounts |
session | List of test accounts |
def test_workflow(gl_client, default_account, accounts):
factory = get_contract_factory("MyContract")
contract = factory.deploy(account=default_account)
tx_receipt = contract.some_method(args=["value"], account=accounts[1])
assert tx_execution_succeeded(tx_receipt)
Mock LLM Responses
Simulate LLM responses for deterministic tests:
from gltest import get_contract_factory, get_validator_factory
from gltest.types import MockedLLMResponse
mock_response: MockedLLMResponse = {
"nondet_exec_prompt": {
"analyze this": "positive sentiment"
},
"eq_principle_prompt_comparative": {
"values match": True
}
}
validator_factory = get_validator_factory()
validators = validator_factory.batch_create_mock_validators(
count=5,
mock_llm_response=mock_response
)
transaction_context = {
"validators": [v.to_dict() for v in validators],
"genvm_datetime": "2024-01-01T00:00:00Z"
}
factory = get_contract_factory("LLMContract")
contract = factory.deploy(transaction_context=transaction_context)
result = contract.analyze_text(args=["analyze this"]).transact(
transaction_context=transaction_context
)
Mock keys map to GenLayer methods:
| Mock Key | GenLayer Method |
|---|---|
"nondet_exec_prompt" |
gl.nondet.exec_prompt |
"eq_principle_prompt_comparative" |
gl.eq_principle.prompt_comparative |
"eq_principle_prompt_non_comparative" |
gl.eq_principle.prompt_non_comparative |
The system performs substring matching on the internal user message — your mock key must appear within the message.
Mock Web Responses
Simulate HTTP responses for contracts that call gl.nondet.web.get(), etc.:
from gltest.types import MockedWebResponse
import json
mock_web_response: MockedWebResponse = {
"nondet_web_request": {
"https://api.example.com/price": {
"method": "GET",
"status": 200,
"body": json.dumps({"price": 100.50})
}
}
}
validators = validator_factory.batch_create_mock_validators(
count=5,
mock_web_response=mock_web_response
)
You can combine both mock_llm_response and mock_web_response in a single batch_create_mock_validators call. URL matching is exact (including query parameters).
Custom Validators
from gltest import get_validator_factory
factory = get_validator_factory()
# Real validators with specific LLM providers
validators = factory.batch_create_validators(
count=5,
stake=10,
provider="openai",
model="gpt-4o",
config={"temperature": 0.7},
plugin="openai-compatible",
plugin_config={"api_key_env_var": "OPENAI_API_KEY"}
)
# Use in transaction context
transaction_context = {
"validators": [v.to_dict() for v in validators],
"genvm_datetime": "2024-03-15T14:30:00Z"
}
Statistical Analysis
For LLM-based contracts, .analyze() runs multiple simulations to measure consistency:
analysis = contract.process_with_llm(args=["input"]).analyze(
provider="openai",
model="gpt-4o",
runs=100,
)
print(f"Success rate: {analysis.success_rate:.2f}%")
print(f"Reliability: {analysis.reliability_score:.2f}%")
print(f"Unique states: {analysis.unique_states}")
Full Studio Mode Documentation — configuration reference, all CLI flags, mock LLM/web details, custom validators, statistical analysis, and complete examples.
Example Contract
from genlayer import *
class Storage(gl.Contract):
storage: str
def __init__(self, initial_storage: str):
self.storage = initial_storage
@gl.public.view
def get_storage(self) -> str:
return self.storage
@gl.public.write
def update_storage(self, new_storage: str) -> None:
self.storage = new_storage
Project Structure
my-project/
├── contracts/
│ └── Storage.py
├── tests/
│ ├── test_direct.py # Direct mode tests (fast)
│ └── test_integration.py # Studio mode tests
└── gltest.config.yaml # Studio mode config
For more examples, see the contracts directory.
Troubleshooting
Contract not found: Ensure contracts are in contracts/ or specify --contracts-dir. Contracts must inherit from gl.Contract.
Transaction timeouts (Studio mode): Increase wait_interval and wait_retries in .transact().
Consensus failures (Studio mode): Increase consensus_max_rotations or use --leader-only for faster iteration.
Environment issues: Verify Python >= 3.12. For Studio mode, check Docker is running (docker ps).
Contributing
See our Contributing Guide.
License
MIT — see LICENSE.
Support
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file genlayer_test-0.29.2.tar.gz.
File metadata
- Download URL: genlayer_test-0.29.2.tar.gz
- Upload date:
- Size: 69.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d76861d00c206de18f258fc09f796e1dd32a1806e377981a7c9ff00d4acf921b
|
|
| MD5 |
5ccf61053b276f44e8675ba78ffbcc5c
|
|
| BLAKE2b-256 |
c53ad76c9528d09deeab5b17828a5a843f54ca6e91ae7653fbd782b6e8832fe9
|
File details
Details for the file genlayer_test-0.29.2-py3-none-any.whl.
File metadata
- Download URL: genlayer_test-0.29.2-py3-none-any.whl
- Upload date:
- Size: 84.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42785984f108f28a6a30006e861f35b74ac7ddae9f17387ddfdf402ac48471ce
|
|
| MD5 |
5f017bc82dfbdc844cec326cdf93b8a4
|
|
| BLAKE2b-256 |
3aafb30644f7cd96e1db46af676b8d5fe51c253a826d103f7465167251a1230f
|