genlayer-test 0.10.1__tar.gz → 0.12.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.
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/LICENSE +2 -2
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/PKG-INFO +271 -6
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/README.md +270 -5
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/PKG-INFO +271 -6
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/SOURCES.txt +7 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/entry_points.txt +1 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/top_level.txt +1 -0
- genlayer_test-0.12.0/gltest/direct/__init__.py +31 -0
- genlayer_test-0.12.0/gltest/direct/loader.py +288 -0
- genlayer_test-0.12.0/gltest/direct/pytest_plugin.py +117 -0
- genlayer_test-0.12.0/gltest/direct/sdk_loader.py +260 -0
- genlayer_test-0.12.0/gltest/direct/types.py +18 -0
- genlayer_test-0.12.0/gltest/direct/vm.py +432 -0
- genlayer_test-0.12.0/gltest/direct/wasi_mock.py +219 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/types.py +14 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/validators/validator_factory.py +37 -11
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/plugin.py +8 -6
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/pyproject.toml +2 -1
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/dependency_links.txt +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/genlayer_test.egg-info/requires.txt +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/accounts.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/artifacts/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/artifacts/contract.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/assertions.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/clients.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/contract.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/contract_factory.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/contract_functions.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/method_stats.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/stats_collector.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/contracts/utils.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/exceptions.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/fixtures.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/helpers/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/helpers/fixture_snapshot.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/helpers/take_snapshot.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/logging.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/utils.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest/validators/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/__init__.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/constants.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/general.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/pytest_context.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/types.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/config/user.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/logging.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/gltest_cli/main.py +0 -0
- {genlayer_test-0.10.1 → genlayer_test-0.12.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024
|
|
3
|
+
Copyright (c) 2024 GenLayer Labs
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: genlayer-test
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: GenLayer Testing Suite
|
|
5
5
|
Author: GenLayer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -24,8 +24,8 @@ Dynamic: license-file
|
|
|
24
24
|
# GenLayer Testing Suite
|
|
25
25
|
|
|
26
26
|
[](https://opensource.org/license/mit/)
|
|
27
|
-
[](https://discord.gg/
|
|
28
|
-
[](https://discord.gg/qjCU4AWnKE)
|
|
28
|
+
[](https://x.com/GenLayer)
|
|
29
29
|
[](https://badge.fury.io/py/genlayer-test)
|
|
30
30
|
[](https://docs.genlayer.com/api-references/genlayer-test)
|
|
31
31
|
[](https://github.com/psf/black)
|
|
@@ -107,7 +107,7 @@ $ pip install genlayer-test
|
|
|
107
107
|
|
|
108
108
|
2. Install from source:
|
|
109
109
|
```bash
|
|
110
|
-
$ git clone https://github.com/
|
|
110
|
+
$ git clone https://github.com/genlayerlabs/genlayer-testing-suite
|
|
111
111
|
$ cd genlayer-testing-suite
|
|
112
112
|
$ pip install -e .
|
|
113
113
|
```
|
|
@@ -312,6 +312,72 @@ The chain type determines various behaviors including RPC endpoints, consensus m
|
|
|
312
312
|
- **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
|
|
313
313
|
- **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
|
|
314
314
|
- **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
|
|
315
|
+
- **Direct Execution Mode** – Run contracts directly in Python for ultra-fast unit testing (~ms vs minutes).
|
|
316
|
+
|
|
317
|
+
## ⚡ Direct vs Simulator Mode
|
|
318
|
+
|
|
319
|
+
The testing suite provides two execution modes:
|
|
320
|
+
|
|
321
|
+
| Mode | How it works | Speed | Use case |
|
|
322
|
+
|------|--------------|-------|----------|
|
|
323
|
+
| **Simulator** | Deploy to GenLayer simulator, interact via RPC | ~minutes | Integration tests, consensus validation |
|
|
324
|
+
| **Direct** | Run Python code directly in-memory | ~milliseconds | Unit tests, rapid development |
|
|
325
|
+
|
|
326
|
+
### Quick Start with Direct Mode
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
def test_token_transfer(direct_vm, direct_deploy):
|
|
330
|
+
# Deploy contract directly in Python (no simulator)
|
|
331
|
+
token = direct_deploy("contracts/Token.py", initial_supply=1000)
|
|
332
|
+
|
|
333
|
+
# Create test addresses
|
|
334
|
+
from gltest.direct import create_address
|
|
335
|
+
alice = create_address("alice")
|
|
336
|
+
bob = create_address("bob")
|
|
337
|
+
|
|
338
|
+
# Set sender and interact
|
|
339
|
+
direct_vm.sender = alice
|
|
340
|
+
token.mint(alice, 500)
|
|
341
|
+
token.transfer(bob, 100)
|
|
342
|
+
|
|
343
|
+
assert token.balances[bob] == 100
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Available Fixtures
|
|
347
|
+
|
|
348
|
+
| Fixture | Description |
|
|
349
|
+
|---------|-------------|
|
|
350
|
+
| `direct_vm` | VM context with cheatcodes |
|
|
351
|
+
| `direct_deploy` | Deploy contracts directly |
|
|
352
|
+
| `direct_alice`, `direct_bob`, `direct_charlie` | Test addresses |
|
|
353
|
+
| `direct_owner` | Default sender address |
|
|
354
|
+
| `direct_accounts` | List of 10 test addresses |
|
|
355
|
+
|
|
356
|
+
### Cheatcodes
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Change sender
|
|
360
|
+
direct_vm.sender = alice
|
|
361
|
+
|
|
362
|
+
# Prank (temporary sender change)
|
|
363
|
+
with direct_vm.prank(bob):
|
|
364
|
+
contract.method() # Called as bob
|
|
365
|
+
|
|
366
|
+
# Snapshots
|
|
367
|
+
snap_id = direct_vm.snapshot()
|
|
368
|
+
contract.modify_state()
|
|
369
|
+
direct_vm.revert(snap_id) # State restored
|
|
370
|
+
|
|
371
|
+
# Expect revert
|
|
372
|
+
with direct_vm.expect_revert("Insufficient balance"):
|
|
373
|
+
contract.transfer(bob, 1000000)
|
|
374
|
+
|
|
375
|
+
# Mock web/LLM (for nondet operations)
|
|
376
|
+
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
|
|
377
|
+
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
📖 **[Full Direct Mode Documentation](docs/direct-runner.md)**
|
|
315
381
|
|
|
316
382
|
## 📚 Examples
|
|
317
383
|
|
|
@@ -622,6 +688,205 @@ The `.analyze()` method helps you:
|
|
|
622
688
|
- Benchmark performance across multiple runs
|
|
623
689
|
|
|
624
690
|
|
|
691
|
+
### Mock Web Responses
|
|
692
|
+
|
|
693
|
+
The Mock Web Response system allows you to simulate HTTP responses for web requests made by intelligent contracts using GenLayer's web methods (`gl.nondet.web.get()`, `gl.nondet.web.post()`, etc.). This feature enables deterministic testing of contracts that interact with external web services without making actual HTTP calls.
|
|
694
|
+
|
|
695
|
+
#### Basic Example
|
|
696
|
+
|
|
697
|
+
Here's a simple example of mocking a web API response:
|
|
698
|
+
|
|
699
|
+
```python
|
|
700
|
+
from gltest import get_contract_factory, get_validator_factory
|
|
701
|
+
from gltest.types import MockedWebResponse
|
|
702
|
+
import json
|
|
703
|
+
|
|
704
|
+
def test_simple_web_mock():
|
|
705
|
+
# Define mock web responses
|
|
706
|
+
mock_web_response: MockedWebResponse = {
|
|
707
|
+
"nondet_web_request": {
|
|
708
|
+
"https://api.example.com/price": {
|
|
709
|
+
"method": "GET",
|
|
710
|
+
"status": 200,
|
|
711
|
+
"body": json.dumps({"price": 100.50})
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
# Create validators with mock web responses
|
|
717
|
+
validator_factory = get_validator_factory()
|
|
718
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
719
|
+
count=5,
|
|
720
|
+
mock_web_response=mock_web_response
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Use validators in transaction context
|
|
724
|
+
transaction_context = {"validators": [v.to_dict() for v in validators]}
|
|
725
|
+
|
|
726
|
+
# Deploy and test contract
|
|
727
|
+
factory = get_contract_factory("PriceOracle")
|
|
728
|
+
contract = factory.deploy(transaction_context=transaction_context)
|
|
729
|
+
|
|
730
|
+
# Contract's web requests will receive the mocked response
|
|
731
|
+
result = contract.update_price().transact(transaction_context=transaction_context)
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
#### Supported HTTP Methods
|
|
735
|
+
|
|
736
|
+
Mock web responses support all HTTP methods including GET, POST, PUT, DELETE, PATCH, etc.:
|
|
737
|
+
|
|
738
|
+
```python
|
|
739
|
+
mock_web_response: MockedWebResponse = {
|
|
740
|
+
"nondet_web_request": {
|
|
741
|
+
# GET request
|
|
742
|
+
"https://api.example.com/users/123": {
|
|
743
|
+
"method": "GET",
|
|
744
|
+
"status": 200,
|
|
745
|
+
"body": '{"id": 123, "name": "Alice"}'
|
|
746
|
+
},
|
|
747
|
+
# POST request
|
|
748
|
+
"https://api.example.com/users": {
|
|
749
|
+
"method": "POST",
|
|
750
|
+
"status": 201,
|
|
751
|
+
"body": '{"id": 124, "name": "Bob", "created": true}'
|
|
752
|
+
},
|
|
753
|
+
# DELETE request
|
|
754
|
+
"https://api.example.com/users/123": {
|
|
755
|
+
"method": "DELETE",
|
|
756
|
+
"status": 204,
|
|
757
|
+
"body": ""
|
|
758
|
+
},
|
|
759
|
+
# PUT request
|
|
760
|
+
"https://api.example.com/users/123": {
|
|
761
|
+
"method": "PUT",
|
|
762
|
+
"status": 200,
|
|
763
|
+
"body": '{"id": 123, "name": "Alice Updated"}'
|
|
764
|
+
},
|
|
765
|
+
# Error response
|
|
766
|
+
"https://api.example.com/error": {
|
|
767
|
+
"method": "GET",
|
|
768
|
+
"status": 500,
|
|
769
|
+
"body": "Internal Server Error"
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
#### How It Works
|
|
776
|
+
|
|
777
|
+
When a contract calls any web method (`gl.nondet.web.get()`, `gl.nondet.web.post()`, etc.):
|
|
778
|
+
1. The mock system checks if the URL exists in the mock configuration
|
|
779
|
+
2. If found, it returns the mocked response with the specified status and body
|
|
780
|
+
3. If not found, the actual web request would be made (or fail if network access is disabled)
|
|
781
|
+
|
|
782
|
+
#### Complete Example: Twitter/X Username Storage
|
|
783
|
+
|
|
784
|
+
Here's a real-world example showing how to mock Twitter/X API responses:
|
|
785
|
+
|
|
786
|
+
```python
|
|
787
|
+
# test_x_username_storage.py
|
|
788
|
+
from gltest import get_contract_factory, get_validator_factory
|
|
789
|
+
from gltest.assertions import tx_execution_succeeded
|
|
790
|
+
from gltest.types import MockedWebResponse
|
|
791
|
+
import json
|
|
792
|
+
import urllib.parse
|
|
793
|
+
|
|
794
|
+
def test_x_username_storage():
|
|
795
|
+
# Helper to build URL with query parameters
|
|
796
|
+
def get_username_url(username: str) -> str:
|
|
797
|
+
params = {"user.fields": "public_metrics,verified"}
|
|
798
|
+
return f"https://domain.com/api/twitter/users/by/username/{username}?{urllib.parse.urlencode(params)}"
|
|
799
|
+
|
|
800
|
+
# Define mock responses for different usernames
|
|
801
|
+
mock_web_response: MockedWebResponse = {
|
|
802
|
+
"nondet_web_request": {
|
|
803
|
+
get_username_url("user_a"): {
|
|
804
|
+
"method": "GET",
|
|
805
|
+
"status": 200,
|
|
806
|
+
"body": json.dumps({"username": "user_a", "verified": True})
|
|
807
|
+
},
|
|
808
|
+
get_username_url("user_b"): {
|
|
809
|
+
"method": "GET",
|
|
810
|
+
"status": 200,
|
|
811
|
+
"body": json.dumps({"username": "user_b", "verified": False})
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
# Create validators with mock web responses
|
|
817
|
+
validator_factory = get_validator_factory()
|
|
818
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
819
|
+
count=5,
|
|
820
|
+
mock_web_response=mock_web_response
|
|
821
|
+
)
|
|
822
|
+
transaction_context = {"validators": [v.to_dict() for v in validators]}
|
|
823
|
+
|
|
824
|
+
# Deploy and test contract
|
|
825
|
+
factory = get_contract_factory("XUsernameStorage")
|
|
826
|
+
contract = factory.deploy(transaction_context=transaction_context)
|
|
827
|
+
|
|
828
|
+
# Test updating username - will use mocked response
|
|
829
|
+
tx_receipt = contract.update_username(args=["user_a"]).transact(
|
|
830
|
+
transaction_context=transaction_context
|
|
831
|
+
)
|
|
832
|
+
assert tx_execution_succeeded(tx_receipt)
|
|
833
|
+
|
|
834
|
+
# Verify the username was stored
|
|
835
|
+
username = contract.get_username().call(transaction_context=transaction_context)
|
|
836
|
+
assert username == "user_a"
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### Combining Mock LLM and Web Responses
|
|
840
|
+
|
|
841
|
+
You can combine both mock LLM responses and mock web responses in the same test:
|
|
842
|
+
|
|
843
|
+
```python
|
|
844
|
+
def test_combined_mocks():
|
|
845
|
+
# Define both mock types
|
|
846
|
+
mock_llm_response = {
|
|
847
|
+
"eq_principle_prompt_comparative": {
|
|
848
|
+
"values match": True
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
mock_web_response: MockedWebResponse = {
|
|
853
|
+
"nondet_web_request": {
|
|
854
|
+
"https://api.example.com/data": {
|
|
855
|
+
"method": "GET",
|
|
856
|
+
"status": 200,
|
|
857
|
+
"body": '{"value": 42}'
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
# Create validators with both mock types
|
|
863
|
+
validator_factory = get_validator_factory()
|
|
864
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
865
|
+
count=5,
|
|
866
|
+
mock_llm_response=mock_llm_response,
|
|
867
|
+
mock_web_response=mock_web_response
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Use in your tests...
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
#### Best Practices
|
|
874
|
+
|
|
875
|
+
1. **URL Matching**: URLs must match exactly, including query parameters
|
|
876
|
+
2. **Response Body**: Always provide the body as a string (use `json.dumps()` for JSON data)
|
|
877
|
+
3. **Status Codes**: Use realistic HTTP status codes (200, 404, 500, etc.)
|
|
878
|
+
4. **Method Matching**: Specify the correct HTTP method that your contract uses
|
|
879
|
+
5. **Error Testing**: Mock error responses to test error handling paths
|
|
880
|
+
6. **Deterministic Tests**: Mock web responses ensure tests don't depend on external services
|
|
881
|
+
|
|
882
|
+
#### Notes
|
|
883
|
+
|
|
884
|
+
- Mock web responses are only available when using mock validators
|
|
885
|
+
- URL matching is exact - the full URL including query parameters must match
|
|
886
|
+
- The method field should match the HTTP method used by the contract
|
|
887
|
+
- Useful for testing contracts that interact with external APIs without network dependencies
|
|
888
|
+
- All standard HTTP methods are supported (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
|
|
889
|
+
|
|
625
890
|
### Custom Transaction Context
|
|
626
891
|
|
|
627
892
|
The GenLayer Testing Suite allows you to customize the transaction execution environment by providing a `transaction_context` parameter with custom validators and GenVM datetime settings.
|
|
@@ -1001,8 +1266,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
1001
1266
|
## 💬 Support
|
|
1002
1267
|
|
|
1003
1268
|
- [Documentation](https://docs.genlayer.com/api-references/genlayer-test)
|
|
1004
|
-
- [Discord Community](https://discord.gg/
|
|
1005
|
-
- [GitHub Issues](https://github.com/
|
|
1269
|
+
- [Discord Community](https://discord.gg/qjCU4AWnKE)
|
|
1270
|
+
- [GitHub Issues](https://github.com/genlayerlabs/genlayer-testing-suite/issues)
|
|
1006
1271
|
- [Twitter](https://x.com/GenLayer)
|
|
1007
1272
|
|
|
1008
1273
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# GenLayer Testing Suite
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/license/mit/)
|
|
4
|
-
[](https://discord.gg/
|
|
5
|
-
[](https://discord.gg/qjCU4AWnKE)
|
|
5
|
+
[](https://x.com/GenLayer)
|
|
6
6
|
[](https://badge.fury.io/py/genlayer-test)
|
|
7
7
|
[](https://docs.genlayer.com/api-references/genlayer-test)
|
|
8
8
|
[](https://github.com/psf/black)
|
|
@@ -84,7 +84,7 @@ $ pip install genlayer-test
|
|
|
84
84
|
|
|
85
85
|
2. Install from source:
|
|
86
86
|
```bash
|
|
87
|
-
$ git clone https://github.com/
|
|
87
|
+
$ git clone https://github.com/genlayerlabs/genlayer-testing-suite
|
|
88
88
|
$ cd genlayer-testing-suite
|
|
89
89
|
$ pip install -e .
|
|
90
90
|
```
|
|
@@ -289,6 +289,72 @@ The chain type determines various behaviors including RPC endpoints, consensus m
|
|
|
289
289
|
- **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
|
|
290
290
|
- **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
|
|
291
291
|
- **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
|
|
292
|
+
- **Direct Execution Mode** – Run contracts directly in Python for ultra-fast unit testing (~ms vs minutes).
|
|
293
|
+
|
|
294
|
+
## ⚡ Direct vs Simulator Mode
|
|
295
|
+
|
|
296
|
+
The testing suite provides two execution modes:
|
|
297
|
+
|
|
298
|
+
| Mode | How it works | Speed | Use case |
|
|
299
|
+
|------|--------------|-------|----------|
|
|
300
|
+
| **Simulator** | Deploy to GenLayer simulator, interact via RPC | ~minutes | Integration tests, consensus validation |
|
|
301
|
+
| **Direct** | Run Python code directly in-memory | ~milliseconds | Unit tests, rapid development |
|
|
302
|
+
|
|
303
|
+
### Quick Start with Direct Mode
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
def test_token_transfer(direct_vm, direct_deploy):
|
|
307
|
+
# Deploy contract directly in Python (no simulator)
|
|
308
|
+
token = direct_deploy("contracts/Token.py", initial_supply=1000)
|
|
309
|
+
|
|
310
|
+
# Create test addresses
|
|
311
|
+
from gltest.direct import create_address
|
|
312
|
+
alice = create_address("alice")
|
|
313
|
+
bob = create_address("bob")
|
|
314
|
+
|
|
315
|
+
# Set sender and interact
|
|
316
|
+
direct_vm.sender = alice
|
|
317
|
+
token.mint(alice, 500)
|
|
318
|
+
token.transfer(bob, 100)
|
|
319
|
+
|
|
320
|
+
assert token.balances[bob] == 100
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Available Fixtures
|
|
324
|
+
|
|
325
|
+
| Fixture | Description |
|
|
326
|
+
|---------|-------------|
|
|
327
|
+
| `direct_vm` | VM context with cheatcodes |
|
|
328
|
+
| `direct_deploy` | Deploy contracts directly |
|
|
329
|
+
| `direct_alice`, `direct_bob`, `direct_charlie` | Test addresses |
|
|
330
|
+
| `direct_owner` | Default sender address |
|
|
331
|
+
| `direct_accounts` | List of 10 test addresses |
|
|
332
|
+
|
|
333
|
+
### Cheatcodes
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
# Change sender
|
|
337
|
+
direct_vm.sender = alice
|
|
338
|
+
|
|
339
|
+
# Prank (temporary sender change)
|
|
340
|
+
with direct_vm.prank(bob):
|
|
341
|
+
contract.method() # Called as bob
|
|
342
|
+
|
|
343
|
+
# Snapshots
|
|
344
|
+
snap_id = direct_vm.snapshot()
|
|
345
|
+
contract.modify_state()
|
|
346
|
+
direct_vm.revert(snap_id) # State restored
|
|
347
|
+
|
|
348
|
+
# Expect revert
|
|
349
|
+
with direct_vm.expect_revert("Insufficient balance"):
|
|
350
|
+
contract.transfer(bob, 1000000)
|
|
351
|
+
|
|
352
|
+
# Mock web/LLM (for nondet operations)
|
|
353
|
+
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
|
|
354
|
+
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
📖 **[Full Direct Mode Documentation](docs/direct-runner.md)**
|
|
292
358
|
|
|
293
359
|
## 📚 Examples
|
|
294
360
|
|
|
@@ -599,6 +665,205 @@ The `.analyze()` method helps you:
|
|
|
599
665
|
- Benchmark performance across multiple runs
|
|
600
666
|
|
|
601
667
|
|
|
668
|
+
### Mock Web Responses
|
|
669
|
+
|
|
670
|
+
The Mock Web Response system allows you to simulate HTTP responses for web requests made by intelligent contracts using GenLayer's web methods (`gl.nondet.web.get()`, `gl.nondet.web.post()`, etc.). This feature enables deterministic testing of contracts that interact with external web services without making actual HTTP calls.
|
|
671
|
+
|
|
672
|
+
#### Basic Example
|
|
673
|
+
|
|
674
|
+
Here's a simple example of mocking a web API response:
|
|
675
|
+
|
|
676
|
+
```python
|
|
677
|
+
from gltest import get_contract_factory, get_validator_factory
|
|
678
|
+
from gltest.types import MockedWebResponse
|
|
679
|
+
import json
|
|
680
|
+
|
|
681
|
+
def test_simple_web_mock():
|
|
682
|
+
# Define mock web responses
|
|
683
|
+
mock_web_response: MockedWebResponse = {
|
|
684
|
+
"nondet_web_request": {
|
|
685
|
+
"https://api.example.com/price": {
|
|
686
|
+
"method": "GET",
|
|
687
|
+
"status": 200,
|
|
688
|
+
"body": json.dumps({"price": 100.50})
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
# Create validators with mock web responses
|
|
694
|
+
validator_factory = get_validator_factory()
|
|
695
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
696
|
+
count=5,
|
|
697
|
+
mock_web_response=mock_web_response
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Use validators in transaction context
|
|
701
|
+
transaction_context = {"validators": [v.to_dict() for v in validators]}
|
|
702
|
+
|
|
703
|
+
# Deploy and test contract
|
|
704
|
+
factory = get_contract_factory("PriceOracle")
|
|
705
|
+
contract = factory.deploy(transaction_context=transaction_context)
|
|
706
|
+
|
|
707
|
+
# Contract's web requests will receive the mocked response
|
|
708
|
+
result = contract.update_price().transact(transaction_context=transaction_context)
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Supported HTTP Methods
|
|
712
|
+
|
|
713
|
+
Mock web responses support all HTTP methods including GET, POST, PUT, DELETE, PATCH, etc.:
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
mock_web_response: MockedWebResponse = {
|
|
717
|
+
"nondet_web_request": {
|
|
718
|
+
# GET request
|
|
719
|
+
"https://api.example.com/users/123": {
|
|
720
|
+
"method": "GET",
|
|
721
|
+
"status": 200,
|
|
722
|
+
"body": '{"id": 123, "name": "Alice"}'
|
|
723
|
+
},
|
|
724
|
+
# POST request
|
|
725
|
+
"https://api.example.com/users": {
|
|
726
|
+
"method": "POST",
|
|
727
|
+
"status": 201,
|
|
728
|
+
"body": '{"id": 124, "name": "Bob", "created": true}'
|
|
729
|
+
},
|
|
730
|
+
# DELETE request
|
|
731
|
+
"https://api.example.com/users/123": {
|
|
732
|
+
"method": "DELETE",
|
|
733
|
+
"status": 204,
|
|
734
|
+
"body": ""
|
|
735
|
+
},
|
|
736
|
+
# PUT request
|
|
737
|
+
"https://api.example.com/users/123": {
|
|
738
|
+
"method": "PUT",
|
|
739
|
+
"status": 200,
|
|
740
|
+
"body": '{"id": 123, "name": "Alice Updated"}'
|
|
741
|
+
},
|
|
742
|
+
# Error response
|
|
743
|
+
"https://api.example.com/error": {
|
|
744
|
+
"method": "GET",
|
|
745
|
+
"status": 500,
|
|
746
|
+
"body": "Internal Server Error"
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
#### How It Works
|
|
753
|
+
|
|
754
|
+
When a contract calls any web method (`gl.nondet.web.get()`, `gl.nondet.web.post()`, etc.):
|
|
755
|
+
1. The mock system checks if the URL exists in the mock configuration
|
|
756
|
+
2. If found, it returns the mocked response with the specified status and body
|
|
757
|
+
3. If not found, the actual web request would be made (or fail if network access is disabled)
|
|
758
|
+
|
|
759
|
+
#### Complete Example: Twitter/X Username Storage
|
|
760
|
+
|
|
761
|
+
Here's a real-world example showing how to mock Twitter/X API responses:
|
|
762
|
+
|
|
763
|
+
```python
|
|
764
|
+
# test_x_username_storage.py
|
|
765
|
+
from gltest import get_contract_factory, get_validator_factory
|
|
766
|
+
from gltest.assertions import tx_execution_succeeded
|
|
767
|
+
from gltest.types import MockedWebResponse
|
|
768
|
+
import json
|
|
769
|
+
import urllib.parse
|
|
770
|
+
|
|
771
|
+
def test_x_username_storage():
|
|
772
|
+
# Helper to build URL with query parameters
|
|
773
|
+
def get_username_url(username: str) -> str:
|
|
774
|
+
params = {"user.fields": "public_metrics,verified"}
|
|
775
|
+
return f"https://domain.com/api/twitter/users/by/username/{username}?{urllib.parse.urlencode(params)}"
|
|
776
|
+
|
|
777
|
+
# Define mock responses for different usernames
|
|
778
|
+
mock_web_response: MockedWebResponse = {
|
|
779
|
+
"nondet_web_request": {
|
|
780
|
+
get_username_url("user_a"): {
|
|
781
|
+
"method": "GET",
|
|
782
|
+
"status": 200,
|
|
783
|
+
"body": json.dumps({"username": "user_a", "verified": True})
|
|
784
|
+
},
|
|
785
|
+
get_username_url("user_b"): {
|
|
786
|
+
"method": "GET",
|
|
787
|
+
"status": 200,
|
|
788
|
+
"body": json.dumps({"username": "user_b", "verified": False})
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
# Create validators with mock web responses
|
|
794
|
+
validator_factory = get_validator_factory()
|
|
795
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
796
|
+
count=5,
|
|
797
|
+
mock_web_response=mock_web_response
|
|
798
|
+
)
|
|
799
|
+
transaction_context = {"validators": [v.to_dict() for v in validators]}
|
|
800
|
+
|
|
801
|
+
# Deploy and test contract
|
|
802
|
+
factory = get_contract_factory("XUsernameStorage")
|
|
803
|
+
contract = factory.deploy(transaction_context=transaction_context)
|
|
804
|
+
|
|
805
|
+
# Test updating username - will use mocked response
|
|
806
|
+
tx_receipt = contract.update_username(args=["user_a"]).transact(
|
|
807
|
+
transaction_context=transaction_context
|
|
808
|
+
)
|
|
809
|
+
assert tx_execution_succeeded(tx_receipt)
|
|
810
|
+
|
|
811
|
+
# Verify the username was stored
|
|
812
|
+
username = contract.get_username().call(transaction_context=transaction_context)
|
|
813
|
+
assert username == "user_a"
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
#### Combining Mock LLM and Web Responses
|
|
817
|
+
|
|
818
|
+
You can combine both mock LLM responses and mock web responses in the same test:
|
|
819
|
+
|
|
820
|
+
```python
|
|
821
|
+
def test_combined_mocks():
|
|
822
|
+
# Define both mock types
|
|
823
|
+
mock_llm_response = {
|
|
824
|
+
"eq_principle_prompt_comparative": {
|
|
825
|
+
"values match": True
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
mock_web_response: MockedWebResponse = {
|
|
830
|
+
"nondet_web_request": {
|
|
831
|
+
"https://api.example.com/data": {
|
|
832
|
+
"method": "GET",
|
|
833
|
+
"status": 200,
|
|
834
|
+
"body": '{"value": 42}'
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
# Create validators with both mock types
|
|
840
|
+
validator_factory = get_validator_factory()
|
|
841
|
+
validators = validator_factory.batch_create_mock_validators(
|
|
842
|
+
count=5,
|
|
843
|
+
mock_llm_response=mock_llm_response,
|
|
844
|
+
mock_web_response=mock_web_response
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
# Use in your tests...
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
#### Best Practices
|
|
851
|
+
|
|
852
|
+
1. **URL Matching**: URLs must match exactly, including query parameters
|
|
853
|
+
2. **Response Body**: Always provide the body as a string (use `json.dumps()` for JSON data)
|
|
854
|
+
3. **Status Codes**: Use realistic HTTP status codes (200, 404, 500, etc.)
|
|
855
|
+
4. **Method Matching**: Specify the correct HTTP method that your contract uses
|
|
856
|
+
5. **Error Testing**: Mock error responses to test error handling paths
|
|
857
|
+
6. **Deterministic Tests**: Mock web responses ensure tests don't depend on external services
|
|
858
|
+
|
|
859
|
+
#### Notes
|
|
860
|
+
|
|
861
|
+
- Mock web responses are only available when using mock validators
|
|
862
|
+
- URL matching is exact - the full URL including query parameters must match
|
|
863
|
+
- The method field should match the HTTP method used by the contract
|
|
864
|
+
- Useful for testing contracts that interact with external APIs without network dependencies
|
|
865
|
+
- All standard HTTP methods are supported (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
|
|
866
|
+
|
|
602
867
|
### Custom Transaction Context
|
|
603
868
|
|
|
604
869
|
The GenLayer Testing Suite allows you to customize the transaction execution environment by providing a `transaction_context` parameter with custom validators and GenVM datetime settings.
|
|
@@ -978,8 +1243,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
978
1243
|
## 💬 Support
|
|
979
1244
|
|
|
980
1245
|
- [Documentation](https://docs.genlayer.com/api-references/genlayer-test)
|
|
981
|
-
- [Discord Community](https://discord.gg/
|
|
982
|
-
- [GitHub Issues](https://github.com/
|
|
1246
|
+
- [Discord Community](https://discord.gg/qjCU4AWnKE)
|
|
1247
|
+
- [GitHub Issues](https://github.com/genlayerlabs/genlayer-testing-suite/issues)
|
|
983
1248
|
- [Twitter](https://x.com/GenLayer)
|
|
984
1249
|
|
|
985
1250
|
|