genlayer-test 0.4.1__py3-none-any.whl → 0.5.1__py3-none-any.whl
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.1.dist-info → genlayer_test-0.5.1.dist-info}/METADATA +257 -24
- genlayer_test-0.5.1.dist-info/RECORD +74 -0
- gltest/__init__.py +7 -6
- gltest/{glchain/client.py → clients.py} +1 -1
- gltest/contracts/__init__.py +4 -0
- gltest/contracts/contract.py +205 -0
- gltest/{glchain/contract.py → contracts/contract_factory.py} +47 -144
- gltest/contracts/contract_functions.py +62 -0
- gltest/contracts/method_stats.py +163 -0
- gltest/contracts/stats_collector.py +259 -0
- gltest/contracts/utils.py +12 -0
- gltest/fixtures.py +2 -6
- gltest/helpers/take_snapshot.py +1 -1
- gltest/types.py +1 -0
- gltest_cli/config/constants.py +2 -0
- gltest_cli/config/plugin.py +121 -49
- gltest_cli/config/pytest_context.py +9 -0
- gltest_cli/config/types.py +73 -8
- gltest_cli/config/user.py +71 -28
- gltest_cli/logging.py +3 -2
- tests/examples/contracts/football_prediction_market.py +1 -1
- tests/examples/contracts/intelligent_oracle_factory.py +1 -0
- tests/examples/contracts/multi_file_contract/__init__.py +1 -0
- tests/examples/contracts/multi_tenant_storage.py +3 -1
- tests/examples/tests/test_football_prediction_market.py +2 -2
- tests/examples/tests/test_intelligent_oracle_factory.py +6 -24
- tests/examples/tests/test_llm_erc20.py +5 -5
- tests/examples/tests/test_llm_erc20_analyze.py +50 -0
- tests/examples/tests/test_log_indexer.py +23 -11
- tests/examples/tests/test_multi_file_contract.py +5 -6
- tests/examples/tests/test_multi_read_erc20.py +14 -12
- tests/examples/tests/test_multi_tenant_storage.py +15 -7
- tests/examples/tests/test_read_erc20.py +1 -1
- tests/examples/tests/test_storage.py +4 -4
- tests/examples/tests/test_user_storage.py +20 -10
- tests/examples/tests/test_wizard_of_coin.py +1 -1
- tests/gltest/artifact/test_contract_definition.py +0 -36
- tests/gltest_cli/config/test_config_integration.py +432 -0
- tests/gltest_cli/config/test_general_config.py +406 -0
- tests/gltest_cli/config/test_plugin.py +164 -1
- tests/gltest_cli/config/test_user.py +61 -1
- genlayer_test-0.4.1.dist-info/RECORD +0 -67
- gltest/glchain/__init__.py +0 -16
- tests/examples/tests/test_multi_file_contract_legacy.py +0 -16
- tests/examples/tests/test_storage_legacy.py +0 -24
- {genlayer_test-0.4.1.dist-info → genlayer_test-0.5.1.dist-info}/WHEEL +0 -0
- {genlayer_test-0.4.1.dist-info → genlayer_test-0.5.1.dist-info}/entry_points.txt +0 -0
- {genlayer_test-0.4.1.dist-info → genlayer_test-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {genlayer_test-0.4.1.dist-info → genlayer_test-0.5.1.dist-info}/top_level.txt +0 -0
- /gltest/{glchain/account.py → accounts.py} +0 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
"""
|
2
|
+
Stats collector module for contract method analysis.
|
3
|
+
|
4
|
+
This module contains classes and functions to collect and analyze statistics
|
5
|
+
from contract method executions, simplifying the analyze_method implementation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import time
|
9
|
+
import json
|
10
|
+
import hashlib
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
from typing import List, Dict, Any, Optional
|
13
|
+
from dataclasses import dataclass
|
14
|
+
|
15
|
+
from gltest.clients import get_gl_client
|
16
|
+
from gltest.types import CalldataEncodable
|
17
|
+
from .method_stats import StateGroup, FailedRun, MethodStatsDetailed, MethodStatsSummary
|
18
|
+
from gltest_cli.config.general import get_general_config
|
19
|
+
from gltest_cli.config.pytest_context import get_current_test_nodeid
|
20
|
+
from .utils import safe_filename
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class SimulationConfig:
|
25
|
+
"""Configuration for simulation runs."""
|
26
|
+
|
27
|
+
provider: str
|
28
|
+
model: str
|
29
|
+
config: Optional[Dict[str, Any]] = None
|
30
|
+
plugin: Optional[str] = None
|
31
|
+
plugin_config: Optional[Dict[str, Any]] = None
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass
|
35
|
+
class SimulationResults:
|
36
|
+
"""Results from simulation runs."""
|
37
|
+
|
38
|
+
sim_results: List[Dict[str, Any]]
|
39
|
+
failed_runs: List[FailedRun]
|
40
|
+
server_errors: int
|
41
|
+
execution_time: float
|
42
|
+
timestamp: str
|
43
|
+
|
44
|
+
|
45
|
+
class StatsCollector:
|
46
|
+
"""Collects and analyzes statistics for contract method executions."""
|
47
|
+
|
48
|
+
def __init__(
|
49
|
+
self,
|
50
|
+
contract_address: str,
|
51
|
+
method_name: str,
|
52
|
+
account: Any,
|
53
|
+
args: Optional[List[CalldataEncodable]] = None,
|
54
|
+
):
|
55
|
+
self.contract_address = contract_address
|
56
|
+
self.method_name = method_name
|
57
|
+
self.account = account
|
58
|
+
self.args = args or []
|
59
|
+
self.client = get_gl_client()
|
60
|
+
|
61
|
+
def run_simulations(
|
62
|
+
self, sim_config: SimulationConfig, runs: int
|
63
|
+
) -> SimulationResults:
|
64
|
+
"""Execute multiple simulation runs and collect results."""
|
65
|
+
start_time = time.time()
|
66
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
67
|
+
|
68
|
+
sim_results = []
|
69
|
+
failed_runs_list = []
|
70
|
+
server_errors = 0
|
71
|
+
|
72
|
+
for run_idx in range(runs):
|
73
|
+
try:
|
74
|
+
sim_result = self._execute_single_simulation(sim_config)
|
75
|
+
sim_results.append(sim_result)
|
76
|
+
|
77
|
+
if sim_result.get("execution_result") != "SUCCESS":
|
78
|
+
failed_runs_list.append(
|
79
|
+
self._create_failed_run(run_idx, sim_result)
|
80
|
+
)
|
81
|
+
except Exception as e:
|
82
|
+
server_errors += 1
|
83
|
+
failed_runs_list.append(
|
84
|
+
FailedRun(
|
85
|
+
run=run_idx,
|
86
|
+
error=str(e),
|
87
|
+
error_type="server",
|
88
|
+
genvm_result=None,
|
89
|
+
)
|
90
|
+
)
|
91
|
+
|
92
|
+
execution_time = time.time() - start_time
|
93
|
+
|
94
|
+
return SimulationResults(
|
95
|
+
sim_results=sim_results,
|
96
|
+
failed_runs=failed_runs_list,
|
97
|
+
server_errors=server_errors,
|
98
|
+
execution_time=execution_time,
|
99
|
+
timestamp=timestamp,
|
100
|
+
)
|
101
|
+
|
102
|
+
def _execute_single_simulation(
|
103
|
+
self, sim_config: SimulationConfig
|
104
|
+
) -> Dict[str, Any]:
|
105
|
+
"""Execute a single simulation."""
|
106
|
+
config_dict = {
|
107
|
+
"provider": sim_config.provider,
|
108
|
+
"model": sim_config.model,
|
109
|
+
}
|
110
|
+
|
111
|
+
if (
|
112
|
+
sim_config.config is not None
|
113
|
+
and sim_config.plugin is not None
|
114
|
+
and sim_config.plugin_config is not None
|
115
|
+
):
|
116
|
+
config_dict["config"] = sim_config.config
|
117
|
+
config_dict["plugin"] = sim_config.plugin
|
118
|
+
config_dict["plugin_config"] = sim_config.plugin_config
|
119
|
+
|
120
|
+
return self.client.simulate_write_contract(
|
121
|
+
address=self.contract_address,
|
122
|
+
function_name=self.method_name,
|
123
|
+
account=self.account,
|
124
|
+
args=self.args,
|
125
|
+
sim_config=config_dict,
|
126
|
+
)
|
127
|
+
|
128
|
+
def _create_failed_run(self, run_idx: int, sim_result: Dict[str, Any]) -> FailedRun:
|
129
|
+
"""Create a FailedRun object from a failed simulation result."""
|
130
|
+
return FailedRun(
|
131
|
+
run=run_idx,
|
132
|
+
error=sim_result.get("error", "unknown error"),
|
133
|
+
error_type="simulation",
|
134
|
+
genvm_result={
|
135
|
+
"stderr": sim_result.get("genvm_result", {}).get("stderr", ""),
|
136
|
+
"stdout": sim_result.get("genvm_result", {}).get("stdout", ""),
|
137
|
+
},
|
138
|
+
)
|
139
|
+
|
140
|
+
def analyze_results(
|
141
|
+
self,
|
142
|
+
sim_results: SimulationResults,
|
143
|
+
runs: int,
|
144
|
+
sim_config: SimulationConfig,
|
145
|
+
) -> MethodStatsSummary:
|
146
|
+
"""Analyze simulation results and generate statistics."""
|
147
|
+
state_groups = self._analyze_states(sim_results.sim_results)
|
148
|
+
|
149
|
+
executed_runs = runs - sim_results.server_errors
|
150
|
+
successful_runs = sum(
|
151
|
+
1
|
152
|
+
for sim_receipt in sim_results.sim_results
|
153
|
+
if sim_receipt.get("execution_result") == "SUCCESS"
|
154
|
+
)
|
155
|
+
|
156
|
+
most_common_count = max((group.count for group in state_groups), default=0)
|
157
|
+
reliability_score = (
|
158
|
+
(most_common_count / executed_runs) if executed_runs > 0 else 0.0
|
159
|
+
)
|
160
|
+
|
161
|
+
# Save detailed stats
|
162
|
+
detailed_stats = self._create_detailed_stats(
|
163
|
+
sim_results=sim_results,
|
164
|
+
state_groups=state_groups,
|
165
|
+
runs=runs,
|
166
|
+
executed_runs=executed_runs,
|
167
|
+
successful_runs=successful_runs,
|
168
|
+
most_common_count=most_common_count,
|
169
|
+
reliability_score=reliability_score,
|
170
|
+
sim_config=sim_config,
|
171
|
+
)
|
172
|
+
self._save_detailed_stats(detailed_stats)
|
173
|
+
|
174
|
+
# Return summary
|
175
|
+
return MethodStatsSummary(
|
176
|
+
method=self.method_name,
|
177
|
+
args=self.args,
|
178
|
+
total_runs=runs,
|
179
|
+
server_error_runs=sim_results.server_errors,
|
180
|
+
executed_runs=executed_runs,
|
181
|
+
failed_runs=len(sim_results.failed_runs),
|
182
|
+
successful_runs=successful_runs,
|
183
|
+
unique_states=len(state_groups),
|
184
|
+
most_common_state_count=most_common_count,
|
185
|
+
reliability_score=reliability_score,
|
186
|
+
execution_time=sim_results.execution_time,
|
187
|
+
provider=sim_config.provider,
|
188
|
+
model=sim_config.model,
|
189
|
+
)
|
190
|
+
|
191
|
+
def _analyze_states(self, sim_results: List[Dict[str, Any]]) -> List[StateGroup]:
|
192
|
+
"""Analyze contract states from simulation results."""
|
193
|
+
state_counts = {}
|
194
|
+
state_to_hash_str = {}
|
195
|
+
|
196
|
+
for sim_receipt in sim_results:
|
197
|
+
contract_state = sim_receipt.get("contract_state", {})
|
198
|
+
state_json = json.dumps(contract_state, sort_keys=True)
|
199
|
+
state_hash = hashlib.sha256(state_json.encode()).hexdigest()
|
200
|
+
state_hash_str = f"0x{state_hash}"
|
201
|
+
state_to_hash_str[state_hash] = state_hash_str
|
202
|
+
state_counts[state_hash] = state_counts.get(state_hash, 0) + 1
|
203
|
+
|
204
|
+
return [
|
205
|
+
StateGroup(count=count, state_hash=state_to_hash_str[state_hash])
|
206
|
+
for state_hash, count in sorted(
|
207
|
+
state_counts.items(), key=lambda x: x[1], reverse=True
|
208
|
+
)
|
209
|
+
]
|
210
|
+
|
211
|
+
def _create_detailed_stats(
|
212
|
+
self,
|
213
|
+
sim_results: SimulationResults,
|
214
|
+
state_groups: List[StateGroup],
|
215
|
+
runs: int,
|
216
|
+
executed_runs: int,
|
217
|
+
successful_runs: int,
|
218
|
+
most_common_count: int,
|
219
|
+
reliability_score: float,
|
220
|
+
sim_config: SimulationConfig,
|
221
|
+
) -> MethodStatsDetailed:
|
222
|
+
"""Create detailed statistics object."""
|
223
|
+
configuration = {
|
224
|
+
"runs": runs,
|
225
|
+
"provider": sim_config.provider,
|
226
|
+
"model": sim_config.model,
|
227
|
+
"config": sim_config.config,
|
228
|
+
"plugin": sim_config.plugin,
|
229
|
+
"plugin_config": sim_config.plugin_config,
|
230
|
+
}
|
231
|
+
|
232
|
+
return MethodStatsDetailed(
|
233
|
+
method=self.method_name,
|
234
|
+
params=self.args,
|
235
|
+
timestamp=sim_results.timestamp,
|
236
|
+
configuration=configuration,
|
237
|
+
execution_time=sim_results.execution_time,
|
238
|
+
executed_runs=executed_runs,
|
239
|
+
failed_runs=len(sim_results.failed_runs),
|
240
|
+
successful_runs=successful_runs,
|
241
|
+
server_error_runs=sim_results.server_errors,
|
242
|
+
unique_states=len(state_groups),
|
243
|
+
most_common_state_count=most_common_count,
|
244
|
+
reliability_score=reliability_score,
|
245
|
+
state_groups=state_groups,
|
246
|
+
failed_runs_results=sim_results.failed_runs,
|
247
|
+
sim_results=sim_results.sim_results,
|
248
|
+
)
|
249
|
+
|
250
|
+
def _save_detailed_stats(self, detailed_stats: MethodStatsDetailed) -> None:
|
251
|
+
"""Save detailed statistics to the configured directory."""
|
252
|
+
general_config = get_general_config()
|
253
|
+
current_nodeid = get_current_test_nodeid()
|
254
|
+
if current_nodeid is None:
|
255
|
+
safe_name = "no_test"
|
256
|
+
else:
|
257
|
+
safe_name = safe_filename(current_nodeid)
|
258
|
+
stats_dir = general_config.get_analysis_dir() / safe_name
|
259
|
+
detailed_stats.save_to_directory(stats_dir)
|
gltest/fixtures.py
CHANGED
@@ -4,12 +4,8 @@ These fixtures can be imported and used in test files.
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import pytest
|
7
|
-
from gltest.
|
8
|
-
|
9
|
-
get_accounts,
|
10
|
-
get_default_account,
|
11
|
-
get_gl_provider,
|
12
|
-
)
|
7
|
+
from gltest.clients import get_gl_client, get_gl_provider
|
8
|
+
from gltest.accounts import get_accounts, get_default_account
|
13
9
|
from gltest_cli.config.general import get_general_config
|
14
10
|
|
15
11
|
|
gltest/helpers/take_snapshot.py
CHANGED
gltest/types.py
CHANGED
gltest_cli/config/constants.py
CHANGED
@@ -4,7 +4,9 @@ from pathlib import Path
|
|
4
4
|
|
5
5
|
GLTEST_CONFIG_FILE = "gltest.config.yaml"
|
6
6
|
DEFAULT_NETWORK = "localnet"
|
7
|
+
PRECONFIGURED_NETWORKS = ["localnet", "studionet", "testnet_asimov"]
|
7
8
|
DEFAULT_RPC_URL = SIMULATOR_JSON_RPC_URL
|
8
9
|
DEFAULT_ENVIRONMENT = ".env"
|
9
10
|
DEFAULT_CONTRACTS_DIR = Path("contracts")
|
11
|
+
DEFAULT_ARTIFACTS_DIR = Path("artifacts")
|
10
12
|
DEFAULT_NETWORK_ID = 61999
|
gltest_cli/config/plugin.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
import pytest
|
1
2
|
from pathlib import Path
|
3
|
+
import shutil
|
2
4
|
from gltest_cli.logging import logger
|
3
5
|
from gltest_cli.config.user import (
|
4
6
|
user_config_exists,
|
@@ -9,6 +11,7 @@ from gltest_cli.config.general import (
|
|
9
11
|
get_general_config,
|
10
12
|
)
|
11
13
|
from gltest_cli.config.types import PluginConfig
|
14
|
+
from gltest_cli.config.pytest_context import _pytest_context
|
12
15
|
|
13
16
|
|
14
17
|
def pytest_addoption(parser):
|
@@ -20,17 +23,24 @@ def pytest_addoption(parser):
|
|
20
23
|
help="Path to directory containing contract files",
|
21
24
|
)
|
22
25
|
|
26
|
+
group.addoption(
|
27
|
+
"--artifacts-dir",
|
28
|
+
action="store",
|
29
|
+
default=None,
|
30
|
+
help="Path to directory for storing contract artifacts",
|
31
|
+
)
|
32
|
+
|
23
33
|
group.addoption(
|
24
34
|
"--default-wait-interval",
|
25
35
|
action="store",
|
26
|
-
default=
|
36
|
+
default=3000,
|
27
37
|
help="Default interval (ms) between transaction receipt checks",
|
28
38
|
)
|
29
39
|
|
30
40
|
group.addoption(
|
31
41
|
"--default-wait-retries",
|
32
42
|
action="store",
|
33
|
-
default=
|
43
|
+
default=50,
|
34
44
|
help="Default number of retries for transaction receipt checks",
|
35
45
|
)
|
36
46
|
|
@@ -55,61 +65,123 @@ def pytest_addoption(parser):
|
|
55
65
|
help="Test with mocks",
|
56
66
|
)
|
57
67
|
|
68
|
+
group.addoption(
|
69
|
+
"--leader-only",
|
70
|
+
action="store_true",
|
71
|
+
default=False,
|
72
|
+
help="Run contracts in leader-only mode",
|
73
|
+
)
|
58
74
|
|
59
|
-
def pytest_configure(config):
|
60
|
-
general_config = get_general_config()
|
61
75
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
76
|
+
def pytest_configure(config):
|
77
|
+
try:
|
78
|
+
general_config = get_general_config()
|
79
|
+
|
80
|
+
network_name = config.getoption("--network")
|
81
|
+
|
82
|
+
if not user_config_exists():
|
83
|
+
logger.warning(
|
84
|
+
"File `gltest.config.yaml` not found in the current directory, using default config, create a `gltest.config.yaml` file to manage multiple networks"
|
85
|
+
)
|
86
|
+
user_config = get_default_user_config()
|
87
|
+
|
88
|
+
# Special handling for testnet_asimov - check if accounts are configured
|
89
|
+
if network_name == "testnet_asimov":
|
90
|
+
logger.error(
|
91
|
+
"For testnet_asimov, you need to configure accounts in gltest.config.yaml, see https://docs.genlayer.com/api-references/genlayer-test"
|
92
|
+
)
|
93
|
+
pytest.exit("gltest configuration error")
|
94
|
+
else:
|
95
|
+
logger.info(
|
96
|
+
"File `gltest.config.yaml` found in the current directory, using it"
|
97
|
+
)
|
98
|
+
user_config = load_user_config("gltest.config.yaml")
|
99
|
+
|
100
|
+
general_config.user_config = user_config
|
101
|
+
|
102
|
+
# Handle plugin config from command line
|
103
|
+
contracts_dir = config.getoption("--contracts-dir")
|
104
|
+
artifacts_dir = config.getoption("--artifacts-dir")
|
105
|
+
default_wait_interval = config.getoption("--default-wait-interval")
|
106
|
+
default_wait_retries = config.getoption("--default-wait-retries")
|
107
|
+
rpc_url = config.getoption("--rpc-url")
|
108
|
+
network = config.getoption("--network")
|
109
|
+
test_with_mocks = config.getoption("--test-with-mocks")
|
110
|
+
leader_only = config.getoption("--leader-only")
|
111
|
+
|
112
|
+
plugin_config = PluginConfig()
|
113
|
+
plugin_config.contracts_dir = (
|
114
|
+
Path(contracts_dir) if contracts_dir is not None else None
|
66
115
|
)
|
67
|
-
|
68
|
-
|
69
|
-
else:
|
70
|
-
logger.info(
|
71
|
-
"File `gltest.config.yaml` found in the current directory, using it"
|
116
|
+
plugin_config.artifacts_dir = (
|
117
|
+
Path(artifacts_dir) if artifacts_dir is not None else None
|
72
118
|
)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
default_wait_interval = config.getoption("--default-wait-interval")
|
80
|
-
default_wait_retries = config.getoption("--default-wait-retries")
|
81
|
-
rpc_url = config.getoption("--rpc-url")
|
82
|
-
network = config.getoption("--network")
|
83
|
-
test_with_mocks = config.getoption("--test-with-mocks")
|
119
|
+
plugin_config.default_wait_interval = int(default_wait_interval)
|
120
|
+
plugin_config.default_wait_retries = int(default_wait_retries)
|
121
|
+
plugin_config.rpc_url = rpc_url
|
122
|
+
plugin_config.network_name = network
|
123
|
+
plugin_config.test_with_mocks = test_with_mocks
|
124
|
+
plugin_config.leader_only = leader_only
|
84
125
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
plugin_config.default_wait_interval = int(default_wait_interval)
|
90
|
-
plugin_config.default_wait_retries = int(default_wait_retries)
|
91
|
-
plugin_config.rpc_url = rpc_url
|
92
|
-
plugin_config.network_name = network
|
93
|
-
plugin_config.test_with_mocks = test_with_mocks
|
94
|
-
|
95
|
-
general_config.plugin_config = plugin_config
|
126
|
+
general_config.plugin_config = plugin_config
|
127
|
+
except Exception as e:
|
128
|
+
logger.error(f"Gltest configure error: {e}")
|
129
|
+
pytest.exit("gltest configuration error")
|
96
130
|
|
97
131
|
|
98
132
|
def pytest_sessionstart(session):
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
133
|
+
try:
|
134
|
+
general_config = get_general_config()
|
135
|
+
artifacts_dir = general_config.get_artifacts_dir()
|
136
|
+
if artifacts_dir and artifacts_dir.exists():
|
137
|
+
logger.info(f"Clearing artifacts directory: {artifacts_dir}")
|
138
|
+
try:
|
139
|
+
shutil.rmtree(artifacts_dir)
|
140
|
+
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
141
|
+
except Exception as e:
|
142
|
+
logger.warning(f"Failed to clear artifacts directory: {e}")
|
143
|
+
elif artifacts_dir:
|
144
|
+
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
145
|
+
logger.info("Using the following configuration:")
|
146
|
+
logger.info(f" RPC URL: {general_config.get_rpc_url()}")
|
147
|
+
logger.info(f" Selected Network: {general_config.get_network_name()}")
|
148
|
+
# Show available networks including preconfigured ones
|
149
|
+
all_networks = general_config.get_networks_keys()
|
150
|
+
logger.info(f" Available networks: {all_networks}")
|
151
|
+
logger.info(f" Contracts directory: {general_config.get_contracts_dir()}")
|
152
|
+
logger.info(f" Artifacts directory: {general_config.get_artifacts_dir()}")
|
153
|
+
logger.info(f" Environment: {general_config.user_config.environment}")
|
154
|
+
logger.info(
|
155
|
+
f" Default wait interval: {general_config.get_default_wait_interval()} ms"
|
156
|
+
)
|
157
|
+
logger.info(
|
158
|
+
f" Default wait retries: {general_config.get_default_wait_retries()}"
|
159
|
+
)
|
160
|
+
logger.info(f" Test with mocks: {general_config.get_test_with_mocks()}")
|
161
|
+
|
162
|
+
if (
|
163
|
+
general_config.get_leader_only()
|
164
|
+
and not general_config.check_studio_based_rpc()
|
165
|
+
):
|
166
|
+
logger.warning(
|
167
|
+
"Leader only mode: True (enabled on non-studio network - will have no effect)"
|
168
|
+
)
|
169
|
+
else:
|
170
|
+
logger.info(f" Leader only mode: {general_config.get_leader_only()}")
|
171
|
+
except Exception as e:
|
172
|
+
logger.error(f"Gltest session start error: {e}")
|
173
|
+
pytest.exit("gltest session start error")
|
174
|
+
|
175
|
+
|
176
|
+
def pytest_runtest_setup(item):
|
177
|
+
_pytest_context.current_item = item
|
178
|
+
|
179
|
+
|
180
|
+
def pytest_runtest_teardown(item):
|
181
|
+
try:
|
182
|
+
del _pytest_context.current_item
|
183
|
+
except AttributeError:
|
184
|
+
pass
|
113
185
|
|
114
186
|
|
115
187
|
pytest_plugins = ["gltest.fixtures"]
|
gltest_cli/config/types.py
CHANGED
@@ -2,24 +2,22 @@ from enum import Enum
|
|
2
2
|
from dataclasses import dataclass, field
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Dict, List, Optional
|
5
|
-
from genlayer_py.chains import localnet, testnet_asimov
|
5
|
+
from genlayer_py.chains import localnet, studionet, testnet_asimov
|
6
6
|
from genlayer_py.types import GenLayerChain
|
7
7
|
from urllib.parse import urlparse
|
8
|
-
|
9
|
-
|
10
|
-
class NetworkConfig(str, Enum):
|
11
|
-
LOCALNET = "localnet"
|
12
|
-
TESTNET_ASIMOV = "testnet_asimov"
|
8
|
+
from gltest_cli.config.constants import PRECONFIGURED_NETWORKS
|
13
9
|
|
14
10
|
|
15
11
|
@dataclass
|
16
12
|
class PluginConfig:
|
17
13
|
contracts_dir: Optional[Path] = None
|
14
|
+
artifacts_dir: Optional[Path] = None
|
18
15
|
rpc_url: Optional[str] = None
|
19
16
|
default_wait_interval: Optional[int] = None
|
20
17
|
default_wait_retries: Optional[int] = None
|
21
18
|
network_name: Optional[str] = None
|
22
19
|
test_with_mocks: bool = False
|
20
|
+
leader_only: bool = False
|
23
21
|
|
24
22
|
|
25
23
|
@dataclass
|
@@ -28,6 +26,7 @@ class NetworkConfigData:
|
|
28
26
|
url: Optional[str] = None
|
29
27
|
accounts: Optional[List[str]] = None
|
30
28
|
from_account: Optional[str] = None
|
29
|
+
leader_only: bool = False
|
31
30
|
|
32
31
|
def __post_init__(self):
|
33
32
|
if self.id is not None and not isinstance(self.id, int):
|
@@ -46,10 +45,13 @@ class NetworkConfigData:
|
|
46
45
|
@dataclass
|
47
46
|
class PathConfig:
|
48
47
|
contracts: Optional[Path] = None
|
48
|
+
artifacts: Optional[Path] = None
|
49
49
|
|
50
50
|
def __post_init__(self):
|
51
51
|
if self.contracts is not None and not isinstance(self.contracts, (str, Path)):
|
52
52
|
raise ValueError("contracts must be a string or Path")
|
53
|
+
if self.artifacts is not None and not isinstance(self.artifacts, (str, Path)):
|
54
|
+
raise ValueError("artifacts must be a string or Path")
|
53
55
|
|
54
56
|
|
55
57
|
@dataclass
|
@@ -93,10 +95,29 @@ class GeneralConfig:
|
|
93
95
|
def set_contracts_dir(self, contracts_dir: Path):
|
94
96
|
self.plugin_config.contracts_dir = contracts_dir
|
95
97
|
|
98
|
+
def get_artifacts_dir(self) -> Path:
|
99
|
+
if self.plugin_config.artifacts_dir is not None:
|
100
|
+
return self.plugin_config.artifacts_dir
|
101
|
+
return self.user_config.paths.artifacts
|
102
|
+
|
103
|
+
def set_artifacts_dir(self, artifacts_dir: Path):
|
104
|
+
self.plugin_config.artifacts_dir = artifacts_dir
|
105
|
+
|
106
|
+
def get_analysis_dir(self) -> Path:
|
107
|
+
artifacts_dir = self.get_artifacts_dir()
|
108
|
+
return artifacts_dir / "analysis"
|
109
|
+
|
110
|
+
def get_networks_keys(self) -> List[str]:
|
111
|
+
return list(self.user_config.networks.keys())
|
112
|
+
|
96
113
|
def get_rpc_url(self) -> str:
|
97
114
|
if self.plugin_config.rpc_url is not None:
|
98
115
|
return self.plugin_config.rpc_url
|
99
116
|
network_name = self.get_network_name()
|
117
|
+
if network_name not in self.user_config.networks:
|
118
|
+
raise ValueError(
|
119
|
+
f"Unknown network: {network_name}, possible values: {self.get_networks_keys()}"
|
120
|
+
)
|
100
121
|
return self.user_config.networks[network_name].url
|
101
122
|
|
102
123
|
def get_default_account_key(self, network_name: Optional[str] = None) -> str:
|
@@ -110,16 +131,37 @@ class GeneralConfig:
|
|
110
131
|
return self.user_config.networks[self.user_config.default_network].accounts
|
111
132
|
|
112
133
|
def get_chain(self) -> GenLayerChain:
|
134
|
+
network_name = self.get_network_name()
|
135
|
+
if network_name not in self.user_config.networks:
|
136
|
+
raise ValueError(
|
137
|
+
f"Unknown network: {network_name}, possible values: {self.get_networks_keys()}"
|
138
|
+
)
|
139
|
+
|
140
|
+
# Reserved network names
|
141
|
+
chain_map_by_name = {
|
142
|
+
"localnet": localnet,
|
143
|
+
"studionet": studionet,
|
144
|
+
"testnet_asimov": testnet_asimov,
|
145
|
+
}
|
146
|
+
|
147
|
+
if network_name in chain_map_by_name:
|
148
|
+
return chain_map_by_name[network_name]
|
149
|
+
|
150
|
+
if network_name in PRECONFIGURED_NETWORKS:
|
151
|
+
raise ValueError(
|
152
|
+
f"Network {network_name} should be handled by reserved mapping"
|
153
|
+
)
|
154
|
+
|
155
|
+
# Custom networks
|
113
156
|
chain_map_by_id = {
|
114
157
|
61999: localnet,
|
115
158
|
4221: testnet_asimov,
|
116
159
|
}
|
117
|
-
network_name = self.get_network_name()
|
118
160
|
network_id = self.user_config.networks[network_name].id
|
119
161
|
if network_id not in chain_map_by_id:
|
120
162
|
known = ", ".join(map(str, chain_map_by_id.keys()))
|
121
163
|
raise ValueError(
|
122
|
-
f"Unknown network: {
|
164
|
+
f"Unknown network id: {network_id}, possible values: {known}"
|
123
165
|
)
|
124
166
|
return chain_map_by_id[network_id]
|
125
167
|
|
@@ -141,8 +183,31 @@ class GeneralConfig:
|
|
141
183
|
def get_test_with_mocks(self) -> bool:
|
142
184
|
return self.plugin_config.test_with_mocks
|
143
185
|
|
186
|
+
def get_leader_only(self) -> bool:
|
187
|
+
if self.plugin_config.leader_only:
|
188
|
+
return True
|
189
|
+
network_name = self.get_network_name()
|
190
|
+
if network_name in self.user_config.networks:
|
191
|
+
network_config = self.user_config.networks[network_name]
|
192
|
+
return network_config.leader_only
|
193
|
+
return False
|
194
|
+
|
144
195
|
def check_local_rpc(self) -> bool:
|
145
196
|
SUPPORTED_RPC_DOMAINS = ["localhost", "127.0.0.1"]
|
146
197
|
rpc_url = self.get_rpc_url()
|
147
198
|
domain = urlparse(rpc_url).netloc.split(":")[0] # Extract domain without port
|
148
199
|
return domain in SUPPORTED_RPC_DOMAINS
|
200
|
+
|
201
|
+
def check_studio_based_rpc(self) -> bool:
|
202
|
+
SUPPORTED_RPC_DOMAINS = ["localhost", "127.0.0.1"]
|
203
|
+
rpc_url = self.get_rpc_url()
|
204
|
+
domain = urlparse(rpc_url).netloc.split(":")[0] # Extract domain without port
|
205
|
+
|
206
|
+
if domain in SUPPORTED_RPC_DOMAINS:
|
207
|
+
return True
|
208
|
+
|
209
|
+
# Check .genlayer.com or .genlayerlabs.com subdomains
|
210
|
+
if domain.endswith(".genlayer.com") or domain.endswith(".genlayerlabs.com"):
|
211
|
+
return True
|
212
|
+
|
213
|
+
return False
|