genlayer-test 0.4.0__tar.gz → 1.0.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.4.0 → genlayer_test-1.0.0}/PKG-INFO +54 -8
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/README.md +52 -6
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/PKG-INFO +54 -8
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/SOURCES.txt +13 -4
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/requires.txt +1 -1
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/__init__.py +7 -6
- genlayer_test-0.4.0/gltest/glchain/client.py → genlayer_test-1.0.0/gltest/clients.py +1 -1
- genlayer_test-1.0.0/gltest/contracts/__init__.py +4 -0
- genlayer_test-1.0.0/gltest/contracts/contract.py +193 -0
- genlayer_test-0.4.0/gltest/glchain/contract.py → genlayer_test-1.0.0/gltest/contracts/contract_factory.py +17 -135
- genlayer_test-1.0.0/gltest/contracts/contract_functions.py +61 -0
- genlayer_test-1.0.0/gltest/contracts/method_stats.py +163 -0
- genlayer_test-1.0.0/gltest/contracts/stats_collector.py +259 -0
- genlayer_test-1.0.0/gltest/contracts/utils.py +12 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/fixtures.py +2 -6
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/helpers/take_snapshot.py +1 -1
- genlayer_test-1.0.0/gltest/logging.py +17 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/constants.py +1 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/plugin.py +37 -0
- genlayer_test-1.0.0/gltest_cli/config/pytest_context.py +9 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/types.py +16 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/user.py +9 -6
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/logging.py +1 -1
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/pyproject.toml +2 -2
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_football_prediction_market.py +2 -2
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_intelligent_oracle_factory.py +6 -6
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_llm_erc20.py +5 -5
- genlayer_test-1.0.0/tests/examples/tests/test_llm_erc20_analyze.py +50 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_log_indexer.py +23 -11
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_multi_file_contract.py +2 -2
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_multi_file_contract_legacy.py +2 -2
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_multi_read_erc20.py +14 -12
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_multi_tenant_storage.py +11 -7
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_read_erc20.py +1 -1
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_storage.py +4 -4
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_storage_legacy.py +5 -3
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_user_storage.py +20 -10
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/tests/test_wizard_of_coin.py +1 -1
- genlayer_test-1.0.0/tests/gltest_cli/config/test_general_config.py +149 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest_cli/config/test_plugin.py +78 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest_cli/config/test_user.py +51 -1
- genlayer_test-0.4.0/gltest/glchain/__init__.py +0 -16
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/LICENSE +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/dependency_links.txt +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/entry_points.txt +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/genlayer_test.egg-info/top_level.txt +0 -0
- /genlayer_test-0.4.0/gltest/glchain/account.py → /genlayer_test-1.0.0/gltest/accounts.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/artifacts/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/artifacts/contract.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/assertions.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/exceptions.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/helpers/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/helpers/fixture_snapshot.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest/types.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/config/general.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/gltest_cli/main.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/setup.cfg +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/conftest.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/football_prediction_market.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/intelligent_oracle.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/intelligent_oracle_factory.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/llm_erc20.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/log_indexer.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/multi_file_contract/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/multi_file_contract/other.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/multi_read_erc20.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/multi_tenant_storage.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/read_erc20.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/storage.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/user_storage.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/examples/contracts/wizard_of_coin.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/artifact/__init__.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/artifact/contracts/duplicate_ic_contract_1.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/artifact/contracts/duplicate_ic_contract_2.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/artifact/contracts/not_ic_contract.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/artifact/test_contract_definition.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest/assertions/test_assertions.py +0 -0
- {genlayer_test-0.4.0 → genlayer_test-1.0.0}/tests/gltest_cli/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: genlayer-test
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: GenLayer Testing Suite
|
5
5
|
Author: GenLayer
|
6
6
|
License-Expression: MIT
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: pytest
|
17
17
|
Requires-Dist: setuptools>=77.0
|
18
|
-
Requires-Dist: genlayer-py==0.
|
18
|
+
Requires-Dist: genlayer-py==0.7.1
|
19
19
|
Requires-Dist: colorama>=0.4.6
|
20
20
|
Requires-Dist: pyyaml
|
21
21
|
Requires-Dist: python-dotenv
|
@@ -59,8 +59,8 @@ contract = factory.deploy(account=other_account)
|
|
59
59
|
assert contract.account == other_account
|
60
60
|
|
61
61
|
# Interact with the contract
|
62
|
-
result = contract.get_value() # Read method
|
63
|
-
tx_receipt = contract.set_value(args=["new_value"]) # Write method
|
62
|
+
result = contract.get_value().call() # Read method
|
63
|
+
tx_receipt = contract.set_value(args=["new_value"]).transact() # Write method
|
64
64
|
|
65
65
|
assert tx_execution_succeeded(tx_receipt)
|
66
66
|
```
|
@@ -320,7 +320,7 @@ def test_deployment():
|
|
320
320
|
|
321
321
|
### Read Methods
|
322
322
|
|
323
|
-
Reading from the contract
|
323
|
+
Reading from the contract requires calling `.call()` on the method:
|
324
324
|
|
325
325
|
```python
|
326
326
|
from gltest import get_contract_factory
|
@@ -332,7 +332,7 @@ def test_read_methods():
|
|
332
332
|
contract = factory.deploy()
|
333
333
|
|
334
334
|
# Call a read-only method
|
335
|
-
result = contract.
|
335
|
+
result = contract.get_storage(args=[]).call()
|
336
336
|
|
337
337
|
# Assert the result matches the initial value
|
338
338
|
assert result == "initial_value"
|
@@ -340,7 +340,7 @@ def test_read_methods():
|
|
340
340
|
|
341
341
|
### Write Methods
|
342
342
|
|
343
|
-
Writing to the contract requires transaction
|
343
|
+
Writing to the contract requires calling `.transact()` on the method. Method arguments are passed to the write method, while transaction parameters are passed to `.transact()`:
|
344
344
|
|
345
345
|
```python
|
346
346
|
from gltest import get_contract_factory
|
@@ -354,6 +354,7 @@ def test_write_methods():
|
|
354
354
|
# Call a write method with arguments
|
355
355
|
tx_receipt = contract.update_storage(
|
356
356
|
args=["new_value"], # Method arguments
|
357
|
+
).transact(
|
357
358
|
value=0, # Optional: amount of native currency to send
|
358
359
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
359
360
|
leader_only=False, # Optional: whether to run only on leader
|
@@ -365,7 +366,7 @@ def test_write_methods():
|
|
365
366
|
assert tx_execution_succeeded(tx_receipt)
|
366
367
|
|
367
368
|
# Verify the value was updated
|
368
|
-
assert contract.get_storage() == "new_value"
|
369
|
+
assert contract.get_storage().call() == "new_value"
|
369
370
|
```
|
370
371
|
|
371
372
|
### Assertions
|
@@ -503,6 +504,50 @@ Fixtures help maintain clean, DRY test code by:
|
|
503
504
|
- Ensuring consistent test environments
|
504
505
|
- Managing resource cleanup automatically
|
505
506
|
- Providing appropriate scoping for performance
|
507
|
+
### Statistical Analysis with `.analyze()`
|
508
|
+
|
509
|
+
The GenLayer Testing Suite provides a powerful `.analyze()` method for write operations that performs statistical analysis through multiple simulation runs. This is particularly useful for testing LLM-based contracts where outputs may vary:
|
510
|
+
|
511
|
+
```python
|
512
|
+
from gltest import get_contract_factory
|
513
|
+
|
514
|
+
def test_analyze_method():
|
515
|
+
factory = get_contract_factory("LlmContract")
|
516
|
+
contract = factory.deploy()
|
517
|
+
|
518
|
+
# Analyze a write method's behavior across multiple runs
|
519
|
+
analysis = contract.process_with_llm(args=["input_data"]).analyze(
|
520
|
+
provider="openai", # LLM provider
|
521
|
+
model="gpt-4o", # Model to use
|
522
|
+
runs=100, # Number of simulation runs (default: 100)
|
523
|
+
config=None, # Optional: provider-specific config
|
524
|
+
plugin=None, # Optional: plugin name
|
525
|
+
plugin_config=None, # Optional: plugin configuration
|
526
|
+
)
|
527
|
+
|
528
|
+
# Access analysis results
|
529
|
+
print(f"Method: {analysis.method}")
|
530
|
+
print(f"Success rate: {analysis.success_rate:.2f}%")
|
531
|
+
print(f"Reliability score: {analysis.reliability_score:.2f}%")
|
532
|
+
print(f"Unique states: {analysis.unique_states}")
|
533
|
+
print(f"Execution time: {analysis.execution_time:.1f}s")
|
534
|
+
|
535
|
+
# The analysis returns a MethodStatsSummary object with:
|
536
|
+
# - method: The contract method name
|
537
|
+
# - args: Arguments passed to the method
|
538
|
+
# - total_runs: Total number of simulation runs
|
539
|
+
# - successful_runs: Number of successful executions
|
540
|
+
# - failed_runs: Number of failed executions
|
541
|
+
# - unique_states: Number of unique contract states observed
|
542
|
+
# - reliability_score: Percentage of runs with the most common state
|
543
|
+
# - execution_time: Total time for all simulations
|
544
|
+
```
|
545
|
+
|
546
|
+
The `.analyze()` method helps you:
|
547
|
+
- Test non-deterministic contract methods
|
548
|
+
- Measure consistency of LLM-based operations
|
549
|
+
- Identify edge cases and failure patterns
|
550
|
+
- Benchmark performance across multiple runs
|
506
551
|
|
507
552
|
## 📝 Best Practices
|
508
553
|
|
@@ -546,6 +591,7 @@ Fixtures help maintain clean, DRY test code by:
|
|
546
591
|
```python
|
547
592
|
tx_receipt = contract.set_value(
|
548
593
|
args=["new_value"],
|
594
|
+
).transact(
|
549
595
|
wait_interval=2, # Increase wait interval between status checks
|
550
596
|
wait_retries=20, # Increase number of retry attempts
|
551
597
|
)
|
@@ -36,8 +36,8 @@ contract = factory.deploy(account=other_account)
|
|
36
36
|
assert contract.account == other_account
|
37
37
|
|
38
38
|
# Interact with the contract
|
39
|
-
result = contract.get_value() # Read method
|
40
|
-
tx_receipt = contract.set_value(args=["new_value"]) # Write method
|
39
|
+
result = contract.get_value().call() # Read method
|
40
|
+
tx_receipt = contract.set_value(args=["new_value"]).transact() # Write method
|
41
41
|
|
42
42
|
assert tx_execution_succeeded(tx_receipt)
|
43
43
|
```
|
@@ -297,7 +297,7 @@ def test_deployment():
|
|
297
297
|
|
298
298
|
### Read Methods
|
299
299
|
|
300
|
-
Reading from the contract
|
300
|
+
Reading from the contract requires calling `.call()` on the method:
|
301
301
|
|
302
302
|
```python
|
303
303
|
from gltest import get_contract_factory
|
@@ -309,7 +309,7 @@ def test_read_methods():
|
|
309
309
|
contract = factory.deploy()
|
310
310
|
|
311
311
|
# Call a read-only method
|
312
|
-
result = contract.
|
312
|
+
result = contract.get_storage(args=[]).call()
|
313
313
|
|
314
314
|
# Assert the result matches the initial value
|
315
315
|
assert result == "initial_value"
|
@@ -317,7 +317,7 @@ def test_read_methods():
|
|
317
317
|
|
318
318
|
### Write Methods
|
319
319
|
|
320
|
-
Writing to the contract requires transaction
|
320
|
+
Writing to the contract requires calling `.transact()` on the method. Method arguments are passed to the write method, while transaction parameters are passed to `.transact()`:
|
321
321
|
|
322
322
|
```python
|
323
323
|
from gltest import get_contract_factory
|
@@ -331,6 +331,7 @@ def test_write_methods():
|
|
331
331
|
# Call a write method with arguments
|
332
332
|
tx_receipt = contract.update_storage(
|
333
333
|
args=["new_value"], # Method arguments
|
334
|
+
).transact(
|
334
335
|
value=0, # Optional: amount of native currency to send
|
335
336
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
336
337
|
leader_only=False, # Optional: whether to run only on leader
|
@@ -342,7 +343,7 @@ def test_write_methods():
|
|
342
343
|
assert tx_execution_succeeded(tx_receipt)
|
343
344
|
|
344
345
|
# Verify the value was updated
|
345
|
-
assert contract.get_storage() == "new_value"
|
346
|
+
assert contract.get_storage().call() == "new_value"
|
346
347
|
```
|
347
348
|
|
348
349
|
### Assertions
|
@@ -480,6 +481,50 @@ Fixtures help maintain clean, DRY test code by:
|
|
480
481
|
- Ensuring consistent test environments
|
481
482
|
- Managing resource cleanup automatically
|
482
483
|
- Providing appropriate scoping for performance
|
484
|
+
### Statistical Analysis with `.analyze()`
|
485
|
+
|
486
|
+
The GenLayer Testing Suite provides a powerful `.analyze()` method for write operations that performs statistical analysis through multiple simulation runs. This is particularly useful for testing LLM-based contracts where outputs may vary:
|
487
|
+
|
488
|
+
```python
|
489
|
+
from gltest import get_contract_factory
|
490
|
+
|
491
|
+
def test_analyze_method():
|
492
|
+
factory = get_contract_factory("LlmContract")
|
493
|
+
contract = factory.deploy()
|
494
|
+
|
495
|
+
# Analyze a write method's behavior across multiple runs
|
496
|
+
analysis = contract.process_with_llm(args=["input_data"]).analyze(
|
497
|
+
provider="openai", # LLM provider
|
498
|
+
model="gpt-4o", # Model to use
|
499
|
+
runs=100, # Number of simulation runs (default: 100)
|
500
|
+
config=None, # Optional: provider-specific config
|
501
|
+
plugin=None, # Optional: plugin name
|
502
|
+
plugin_config=None, # Optional: plugin configuration
|
503
|
+
)
|
504
|
+
|
505
|
+
# Access analysis results
|
506
|
+
print(f"Method: {analysis.method}")
|
507
|
+
print(f"Success rate: {analysis.success_rate:.2f}%")
|
508
|
+
print(f"Reliability score: {analysis.reliability_score:.2f}%")
|
509
|
+
print(f"Unique states: {analysis.unique_states}")
|
510
|
+
print(f"Execution time: {analysis.execution_time:.1f}s")
|
511
|
+
|
512
|
+
# The analysis returns a MethodStatsSummary object with:
|
513
|
+
# - method: The contract method name
|
514
|
+
# - args: Arguments passed to the method
|
515
|
+
# - total_runs: Total number of simulation runs
|
516
|
+
# - successful_runs: Number of successful executions
|
517
|
+
# - failed_runs: Number of failed executions
|
518
|
+
# - unique_states: Number of unique contract states observed
|
519
|
+
# - reliability_score: Percentage of runs with the most common state
|
520
|
+
# - execution_time: Total time for all simulations
|
521
|
+
```
|
522
|
+
|
523
|
+
The `.analyze()` method helps you:
|
524
|
+
- Test non-deterministic contract methods
|
525
|
+
- Measure consistency of LLM-based operations
|
526
|
+
- Identify edge cases and failure patterns
|
527
|
+
- Benchmark performance across multiple runs
|
483
528
|
|
484
529
|
## 📝 Best Practices
|
485
530
|
|
@@ -523,6 +568,7 @@ Fixtures help maintain clean, DRY test code by:
|
|
523
568
|
```python
|
524
569
|
tx_receipt = contract.set_value(
|
525
570
|
args=["new_value"],
|
571
|
+
).transact(
|
526
572
|
wait_interval=2, # Increase wait interval between status checks
|
527
573
|
wait_retries=20, # Increase number of retry attempts
|
528
574
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: genlayer-test
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: GenLayer Testing Suite
|
5
5
|
Author: GenLayer
|
6
6
|
License-Expression: MIT
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: pytest
|
17
17
|
Requires-Dist: setuptools>=77.0
|
18
|
-
Requires-Dist: genlayer-py==0.
|
18
|
+
Requires-Dist: genlayer-py==0.7.1
|
19
19
|
Requires-Dist: colorama>=0.4.6
|
20
20
|
Requires-Dist: pyyaml
|
21
21
|
Requires-Dist: python-dotenv
|
@@ -59,8 +59,8 @@ contract = factory.deploy(account=other_account)
|
|
59
59
|
assert contract.account == other_account
|
60
60
|
|
61
61
|
# Interact with the contract
|
62
|
-
result = contract.get_value() # Read method
|
63
|
-
tx_receipt = contract.set_value(args=["new_value"]) # Write method
|
62
|
+
result = contract.get_value().call() # Read method
|
63
|
+
tx_receipt = contract.set_value(args=["new_value"]).transact() # Write method
|
64
64
|
|
65
65
|
assert tx_execution_succeeded(tx_receipt)
|
66
66
|
```
|
@@ -320,7 +320,7 @@ def test_deployment():
|
|
320
320
|
|
321
321
|
### Read Methods
|
322
322
|
|
323
|
-
Reading from the contract
|
323
|
+
Reading from the contract requires calling `.call()` on the method:
|
324
324
|
|
325
325
|
```python
|
326
326
|
from gltest import get_contract_factory
|
@@ -332,7 +332,7 @@ def test_read_methods():
|
|
332
332
|
contract = factory.deploy()
|
333
333
|
|
334
334
|
# Call a read-only method
|
335
|
-
result = contract.
|
335
|
+
result = contract.get_storage(args=[]).call()
|
336
336
|
|
337
337
|
# Assert the result matches the initial value
|
338
338
|
assert result == "initial_value"
|
@@ -340,7 +340,7 @@ def test_read_methods():
|
|
340
340
|
|
341
341
|
### Write Methods
|
342
342
|
|
343
|
-
Writing to the contract requires transaction
|
343
|
+
Writing to the contract requires calling `.transact()` on the method. Method arguments are passed to the write method, while transaction parameters are passed to `.transact()`:
|
344
344
|
|
345
345
|
```python
|
346
346
|
from gltest import get_contract_factory
|
@@ -354,6 +354,7 @@ def test_write_methods():
|
|
354
354
|
# Call a write method with arguments
|
355
355
|
tx_receipt = contract.update_storage(
|
356
356
|
args=["new_value"], # Method arguments
|
357
|
+
).transact(
|
357
358
|
value=0, # Optional: amount of native currency to send
|
358
359
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
359
360
|
leader_only=False, # Optional: whether to run only on leader
|
@@ -365,7 +366,7 @@ def test_write_methods():
|
|
365
366
|
assert tx_execution_succeeded(tx_receipt)
|
366
367
|
|
367
368
|
# Verify the value was updated
|
368
|
-
assert contract.get_storage() == "new_value"
|
369
|
+
assert contract.get_storage().call() == "new_value"
|
369
370
|
```
|
370
371
|
|
371
372
|
### Assertions
|
@@ -503,6 +504,50 @@ Fixtures help maintain clean, DRY test code by:
|
|
503
504
|
- Ensuring consistent test environments
|
504
505
|
- Managing resource cleanup automatically
|
505
506
|
- Providing appropriate scoping for performance
|
507
|
+
### Statistical Analysis with `.analyze()`
|
508
|
+
|
509
|
+
The GenLayer Testing Suite provides a powerful `.analyze()` method for write operations that performs statistical analysis through multiple simulation runs. This is particularly useful for testing LLM-based contracts where outputs may vary:
|
510
|
+
|
511
|
+
```python
|
512
|
+
from gltest import get_contract_factory
|
513
|
+
|
514
|
+
def test_analyze_method():
|
515
|
+
factory = get_contract_factory("LlmContract")
|
516
|
+
contract = factory.deploy()
|
517
|
+
|
518
|
+
# Analyze a write method's behavior across multiple runs
|
519
|
+
analysis = contract.process_with_llm(args=["input_data"]).analyze(
|
520
|
+
provider="openai", # LLM provider
|
521
|
+
model="gpt-4o", # Model to use
|
522
|
+
runs=100, # Number of simulation runs (default: 100)
|
523
|
+
config=None, # Optional: provider-specific config
|
524
|
+
plugin=None, # Optional: plugin name
|
525
|
+
plugin_config=None, # Optional: plugin configuration
|
526
|
+
)
|
527
|
+
|
528
|
+
# Access analysis results
|
529
|
+
print(f"Method: {analysis.method}")
|
530
|
+
print(f"Success rate: {analysis.success_rate:.2f}%")
|
531
|
+
print(f"Reliability score: {analysis.reliability_score:.2f}%")
|
532
|
+
print(f"Unique states: {analysis.unique_states}")
|
533
|
+
print(f"Execution time: {analysis.execution_time:.1f}s")
|
534
|
+
|
535
|
+
# The analysis returns a MethodStatsSummary object with:
|
536
|
+
# - method: The contract method name
|
537
|
+
# - args: Arguments passed to the method
|
538
|
+
# - total_runs: Total number of simulation runs
|
539
|
+
# - successful_runs: Number of successful executions
|
540
|
+
# - failed_runs: Number of failed executions
|
541
|
+
# - unique_states: Number of unique contract states observed
|
542
|
+
# - reliability_score: Percentage of runs with the most common state
|
543
|
+
# - execution_time: Total time for all simulations
|
544
|
+
```
|
545
|
+
|
546
|
+
The `.analyze()` method helps you:
|
547
|
+
- Test non-deterministic contract methods
|
548
|
+
- Measure consistency of LLM-based operations
|
549
|
+
- Identify edge cases and failure patterns
|
550
|
+
- Benchmark performance across multiple runs
|
506
551
|
|
507
552
|
## 📝 Best Practices
|
508
553
|
|
@@ -546,6 +591,7 @@ Fixtures help maintain clean, DRY test code by:
|
|
546
591
|
```python
|
547
592
|
tx_receipt = contract.set_value(
|
548
593
|
args=["new_value"],
|
594
|
+
).transact(
|
549
595
|
wait_interval=2, # Increase wait interval between status checks
|
550
596
|
wait_retries=20, # Increase number of retry attempts
|
551
597
|
)
|
@@ -8,16 +8,22 @@ genlayer_test.egg-info/entry_points.txt
|
|
8
8
|
genlayer_test.egg-info/requires.txt
|
9
9
|
genlayer_test.egg-info/top_level.txt
|
10
10
|
gltest/__init__.py
|
11
|
+
gltest/accounts.py
|
11
12
|
gltest/assertions.py
|
13
|
+
gltest/clients.py
|
12
14
|
gltest/exceptions.py
|
13
15
|
gltest/fixtures.py
|
16
|
+
gltest/logging.py
|
14
17
|
gltest/types.py
|
15
18
|
gltest/artifacts/__init__.py
|
16
19
|
gltest/artifacts/contract.py
|
17
|
-
gltest/
|
18
|
-
gltest/
|
19
|
-
gltest/
|
20
|
-
gltest/
|
20
|
+
gltest/contracts/__init__.py
|
21
|
+
gltest/contracts/contract.py
|
22
|
+
gltest/contracts/contract_factory.py
|
23
|
+
gltest/contracts/contract_functions.py
|
24
|
+
gltest/contracts/method_stats.py
|
25
|
+
gltest/contracts/stats_collector.py
|
26
|
+
gltest/contracts/utils.py
|
21
27
|
gltest/helpers/__init__.py
|
22
28
|
gltest/helpers/fixture_snapshot.py
|
23
29
|
gltest/helpers/take_snapshot.py
|
@@ -27,6 +33,7 @@ gltest_cli/config/__init__.py
|
|
27
33
|
gltest_cli/config/constants.py
|
28
34
|
gltest_cli/config/general.py
|
29
35
|
gltest_cli/config/plugin.py
|
36
|
+
gltest_cli/config/pytest_context.py
|
30
37
|
gltest_cli/config/types.py
|
31
38
|
gltest_cli/config/user.py
|
32
39
|
tests/__init__.py
|
@@ -47,6 +54,7 @@ tests/examples/contracts/multi_file_contract/other.py
|
|
47
54
|
tests/examples/tests/test_football_prediction_market.py
|
48
55
|
tests/examples/tests/test_intelligent_oracle_factory.py
|
49
56
|
tests/examples/tests/test_llm_erc20.py
|
57
|
+
tests/examples/tests/test_llm_erc20_analyze.py
|
50
58
|
tests/examples/tests/test_log_indexer.py
|
51
59
|
tests/examples/tests/test_multi_file_contract.py
|
52
60
|
tests/examples/tests/test_multi_file_contract_legacy.py
|
@@ -65,5 +73,6 @@ tests/gltest/artifact/contracts/duplicate_ic_contract_2.py
|
|
65
73
|
tests/gltest/artifact/contracts/not_ic_contract.py
|
66
74
|
tests/gltest/assertions/test_assertions.py
|
67
75
|
tests/gltest_cli/__init__.py
|
76
|
+
tests/gltest_cli/config/test_general_config.py
|
68
77
|
tests/gltest_cli/config/test_plugin.py
|
69
78
|
tests/gltest_cli/config/test_user.py
|
@@ -1,14 +1,15 @@
|
|
1
|
-
from gltest.
|
2
|
-
|
1
|
+
from gltest.accounts import (
|
2
|
+
get_default_account,
|
3
|
+
get_accounts,
|
3
4
|
create_accounts,
|
4
|
-
|
5
|
+
create_account,
|
6
|
+
)
|
7
|
+
from gltest.clients import (
|
5
8
|
get_gl_client,
|
6
|
-
get_accounts,
|
7
|
-
get_default_account,
|
8
9
|
)
|
10
|
+
from gltest.contracts import get_contract_factory
|
9
11
|
|
10
12
|
__all__ = [
|
11
|
-
"find_contract_definition",
|
12
13
|
"create_account",
|
13
14
|
"create_accounts",
|
14
15
|
"get_contract_factory",
|
@@ -0,0 +1,193 @@
|
|
1
|
+
import types
|
2
|
+
from eth_account.signers.local import LocalAccount
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from gltest.clients import get_gl_client
|
5
|
+
from gltest.types import CalldataEncodable, GenLayerTransaction, TransactionStatus
|
6
|
+
from typing import List, Any, Optional, Dict, Callable
|
7
|
+
from gltest_cli.config.general import get_general_config
|
8
|
+
from .contract_functions import ContractFunction
|
9
|
+
from .stats_collector import StatsCollector, SimulationConfig
|
10
|
+
|
11
|
+
|
12
|
+
def read_contract_wrapper(
|
13
|
+
self,
|
14
|
+
method_name: str,
|
15
|
+
args: Optional[List[CalldataEncodable]] = None,
|
16
|
+
) -> Any:
|
17
|
+
"""
|
18
|
+
Wrapper to the contract read method.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def call_method():
|
22
|
+
client = get_gl_client()
|
23
|
+
return client.read_contract(
|
24
|
+
address=self.address,
|
25
|
+
function_name=method_name,
|
26
|
+
account=self.account,
|
27
|
+
args=args,
|
28
|
+
)
|
29
|
+
|
30
|
+
return ContractFunction(
|
31
|
+
method_name=method_name,
|
32
|
+
read_only=True,
|
33
|
+
call_method=call_method,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
def write_contract_wrapper(
|
38
|
+
self,
|
39
|
+
method_name: str,
|
40
|
+
args: Optional[List[CalldataEncodable]] = None,
|
41
|
+
) -> GenLayerTransaction:
|
42
|
+
"""
|
43
|
+
Wrapper to the contract write method.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def transact_method(
|
47
|
+
value: int = 0,
|
48
|
+
consensus_max_rotations: Optional[int] = None,
|
49
|
+
leader_only: bool = False,
|
50
|
+
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
51
|
+
wait_interval: Optional[int] = None,
|
52
|
+
wait_retries: Optional[int] = None,
|
53
|
+
wait_triggered_transactions: bool = True,
|
54
|
+
wait_triggered_transactions_status: TransactionStatus = TransactionStatus.FINALIZED,
|
55
|
+
):
|
56
|
+
"""
|
57
|
+
Transact the contract method.
|
58
|
+
"""
|
59
|
+
general_config = get_general_config()
|
60
|
+
actual_wait_interval = (
|
61
|
+
wait_interval
|
62
|
+
if wait_interval is not None
|
63
|
+
else general_config.get_default_wait_interval()
|
64
|
+
)
|
65
|
+
actual_wait_retries = (
|
66
|
+
wait_retries
|
67
|
+
if wait_retries is not None
|
68
|
+
else general_config.get_default_wait_retries()
|
69
|
+
)
|
70
|
+
client = get_gl_client()
|
71
|
+
tx_hash = client.write_contract(
|
72
|
+
address=self.address,
|
73
|
+
function_name=method_name,
|
74
|
+
account=self.account,
|
75
|
+
value=value,
|
76
|
+
consensus_max_rotations=consensus_max_rotations,
|
77
|
+
leader_only=leader_only,
|
78
|
+
args=args,
|
79
|
+
)
|
80
|
+
receipt = client.wait_for_transaction_receipt(
|
81
|
+
transaction_hash=tx_hash,
|
82
|
+
status=wait_transaction_status,
|
83
|
+
interval=actual_wait_interval,
|
84
|
+
retries=actual_wait_retries,
|
85
|
+
)
|
86
|
+
if wait_triggered_transactions:
|
87
|
+
triggered_transactions = receipt["triggered_transactions"]
|
88
|
+
for triggered_transaction in triggered_transactions:
|
89
|
+
client.wait_for_transaction_receipt(
|
90
|
+
transaction_hash=triggered_transaction,
|
91
|
+
status=wait_triggered_transactions_status,
|
92
|
+
interval=actual_wait_interval,
|
93
|
+
retries=actual_wait_retries,
|
94
|
+
)
|
95
|
+
return receipt
|
96
|
+
|
97
|
+
def analyze_method(
|
98
|
+
provider: str,
|
99
|
+
model: str,
|
100
|
+
config: Optional[Dict[str, Any]] = None,
|
101
|
+
plugin: Optional[str] = None,
|
102
|
+
plugin_config: Optional[Dict[str, Any]] = None,
|
103
|
+
runs: int = 100,
|
104
|
+
):
|
105
|
+
"""
|
106
|
+
Analyze the contract method using StatsCollector.
|
107
|
+
"""
|
108
|
+
collector = StatsCollector(
|
109
|
+
contract_address=self.address,
|
110
|
+
method_name=method_name,
|
111
|
+
account=self.account,
|
112
|
+
args=args,
|
113
|
+
)
|
114
|
+
sim_config = SimulationConfig(
|
115
|
+
provider=provider,
|
116
|
+
model=model,
|
117
|
+
config=config,
|
118
|
+
plugin=plugin,
|
119
|
+
plugin_config=plugin_config,
|
120
|
+
)
|
121
|
+
sim_results = collector.run_simulations(sim_config, runs)
|
122
|
+
return collector.analyze_results(sim_results, runs, sim_config)
|
123
|
+
|
124
|
+
return ContractFunction(
|
125
|
+
method_name=method_name,
|
126
|
+
read_only=False,
|
127
|
+
transact_method=transact_method,
|
128
|
+
analyze_method=analyze_method,
|
129
|
+
)
|
130
|
+
|
131
|
+
|
132
|
+
def contract_function_factory(method_name: str, read_only: bool) -> Callable:
|
133
|
+
"""
|
134
|
+
Create a function that interacts with a specific contract method.
|
135
|
+
"""
|
136
|
+
if read_only:
|
137
|
+
return lambda self, args=None: read_contract_wrapper(self, method_name, args)
|
138
|
+
return lambda self, args=None: write_contract_wrapper(self, method_name, args)
|
139
|
+
|
140
|
+
|
141
|
+
@dataclass
|
142
|
+
class Contract:
|
143
|
+
"""
|
144
|
+
Class to interact with a contract, its methods
|
145
|
+
are implemented dynamically at build time.
|
146
|
+
"""
|
147
|
+
|
148
|
+
address: str
|
149
|
+
account: Optional[LocalAccount] = None
|
150
|
+
_schema: Optional[Dict[str, Any]] = None
|
151
|
+
|
152
|
+
@classmethod
|
153
|
+
def new(
|
154
|
+
cls,
|
155
|
+
address: str,
|
156
|
+
schema: Dict[str, Any],
|
157
|
+
account: Optional[LocalAccount] = None,
|
158
|
+
) -> "Contract":
|
159
|
+
"""
|
160
|
+
Build the methods from the schema.
|
161
|
+
"""
|
162
|
+
if not isinstance(schema, dict) or "methods" not in schema:
|
163
|
+
raise ValueError("Invalid schema: must contain 'methods' field")
|
164
|
+
instance = cls(address=address, _schema=schema, account=account)
|
165
|
+
instance._build_methods_from_schema()
|
166
|
+
return instance
|
167
|
+
|
168
|
+
def _build_methods_from_schema(self):
|
169
|
+
"""
|
170
|
+
Build the methods from the schema.
|
171
|
+
"""
|
172
|
+
if self._schema is None:
|
173
|
+
raise ValueError("No schema provided")
|
174
|
+
for method_name, method_info in self._schema["methods"].items():
|
175
|
+
if not isinstance(method_info, dict) or "readonly" not in method_info:
|
176
|
+
raise ValueError(
|
177
|
+
f"Invalid method info for '{method_name}': must contain 'readonly' field"
|
178
|
+
)
|
179
|
+
method_func = contract_function_factory(
|
180
|
+
method_name, method_info["readonly"]
|
181
|
+
)
|
182
|
+
bound_method = types.MethodType(method_func, self)
|
183
|
+
setattr(self, method_name, bound_method)
|
184
|
+
|
185
|
+
def connect(self, account: LocalAccount) -> "Contract":
|
186
|
+
"""
|
187
|
+
Create a new instance of the contract with the same methods and a different account.
|
188
|
+
"""
|
189
|
+
new_contract = self.__class__(
|
190
|
+
address=self.address, account=account, _schema=self._schema
|
191
|
+
)
|
192
|
+
new_contract._build_methods_from_schema()
|
193
|
+
return new_contract
|