Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

GenLayer Testing Suite

License: MIT Discord Twitter PyPI version Documentation Code style: black

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 studio_devnet      # Hosted release preview
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

  studio_devnet:
    # Pre-configured release preview — accounts auto-generated

  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: localnet, studio_devnet, and studionet work out of the box. testnet_asimov requires account keys.
  • Paths: Where your contracts and artifacts live.
  • Environment: .env file 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)

Fee Profiling

Generate a frontend-ready fee profile from the deploys and write transactions executed during a gltest session:

gltest --fee-profile artifacts/fee-profile.json
gltest --fee-profile artifacts/fee-profile.json --fee-profile-headroom 1.5

--fee-profile writes JSON that can be used as developer fee suggestions by transaction-kit, genlayer-js, genlayer-py, or CLI-based submission flows. The optional --fee-profile-headroom multiplier defaults to 1.25. Fee and time-unit values are multiplied by headroom, rounded up, and emitted as decimal strings. When the same method is observed in multiple tests, the profile records the maximum observed value for each field across all of those branches. rotationsPerRound is recorded exactly because it is a posture choice rather than a consumed fee amount.

{
  "version": 1,
  "network": "localnet",
  "chainId": 61127,
  "measuredAt": "2026-06-10T12:00:00Z",
  "deploy": {
    "leaderTimeunitsAllocation": "125",
    "validatorTimeunitsAllocation": "250",
    "executionBudgetPerRound": "625000",
    "totalMessageFees": "0",
    "rotationsPerRound": "0"
  },
  "methods": {
    "create_bet": {
      "leaderTimeunitsAllocation": "125",
      "validatorTimeunitsAllocation": "250",
      "executionBudgetPerRound": "312500",
      "totalMessageFees": "12500",
      "rotationsPerRound": "0"
    }
  }
}

Fee profiling is currently measurable on Studio-based networks whose receipts include consumed fee data. Testnet receipts do not expose consumed fees yet, and direct/sim mode does not go through these receipt paths. Time-unit allocations are recorded from the submitted fee distribution when the backend receipt includes it. Live price caps and feeValue are intentionally omitted so the SDK can quote them from the current network policy at transaction time. The numeric chainId scopes the measurements to the exact selected runtime chain; consumers must fall back to network defaults when it is absent or does not match.

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

import genlayer as gl

class Storage(gl.contract.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.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

Metadata

Release files for genlayer-test 0.30.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for genlayer-test 0.30.0rc1
File Size Uploaded
genlayer_test-0.30.0rc1.tar.gz 83.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for genlayer-test 0.30.0rc1
File Interpreter ABI Platform
genlayer_test-0.30.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 178.8 kB

Release files / genlayer_test-0.30.0rc1.tar.gz

Download URL genlayer_test-0.30.0rc1.tar.gz
Size 83.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7700ab614d9eeef09748861adef1374779a29b69b8dbddc17830100d957cf7b2
BLAKE2b-256 checksum
How to use checksums
415722750e99887c694f9482a2c13cfdeb3e83458a9c05a68d5893c44e1a7415
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}

Release files / genlayer_test-0.30.0rc1-py3-none-any.whl

Download URL genlayer_test-0.30.0rc1-py3-none-any.whl
Size 95.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e66983fef3b18710b2a6a7e1e8c7522bad804130f23cfa0ae857fa9e904cd01
BLAKE2b-256 checksum
How to use checksums
7c33be92ad8078c728c4f21584a54ebd1ee480b77031fbca928bcb7c7a0e32cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}

Release history Release notifications | RSS feed

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.0.0

2 release files

This release

0.30.0rc1 This release

2 release files

0.29.2

2 release files

0.29.1

2 release files

0.29.0

2 release files

0.28.0

2 release files

0.27.3

2 release files

0.27.2

2 release files

0.27.1

2 release files

0.27.0

2 release files

0.26.0

2 release files

0.25.0

2 release files

0.23.0

2 release files

0.22.0

2 release files

0.21.0

2 release files

0.20.4

2 release files

0.20.3

2 release files

0.20.2

2 release files

0.20.1

2 release files

0.20.0

2 release files

0.19.2

2 release files

0.11.0

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page