genlayer-test 0.2.0__py3-none-any.whl → 0.3.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.2.0.dist-info → genlayer_test-0.3.1.dist-info}/METADATA +77 -10
- genlayer_test-0.3.1.dist-info/RECORD +65 -0
- {genlayer_test-0.2.0.dist-info → genlayer_test-0.3.1.dist-info}/entry_points.txt +1 -1
- gltest/__init__.py +4 -4
- gltest/artifacts/contract.py +9 -4
- gltest/glchain/__init__.py +3 -3
- gltest/glchain/account.py +15 -11
- gltest/glchain/client.py +39 -3
- gltest/glchain/contract.py +57 -26
- gltest/helpers/fixture_snapshot.py +3 -2
- gltest_cli/config/__init__.py +0 -0
- gltest_cli/config/constants.py +10 -0
- gltest_cli/config/general.py +10 -0
- gltest_cli/config/plugin.py +102 -0
- gltest_cli/config/types.py +137 -0
- gltest_cli/config/user.py +222 -0
- gltest_cli/logging.py +51 -0
- tests/__init__.py +0 -0
- tests/examples/tests/test_llm_erc20.py +2 -2
- tests/examples/tests/test_multi_read_erc20.py +13 -3
- tests/examples/tests/test_multi_tenant_storage.py +12 -3
- tests/examples/tests/test_read_erc20.py +2 -2
- tests/examples/tests/test_storage.py +4 -2
- tests/examples/tests/test_user_storage.py +17 -3
- tests/gltest/__init__.py +0 -0
- tests/gltest/artifact/__init__.py +0 -0
- tests/gltest/artifact/test_contract_definition.py +91 -0
- tests/gltest_cli/__init__.py +0 -0
- tests/gltest_cli/config/test_plugin.py +127 -0
- tests/gltest_cli/config/test_user.py +351 -0
- genlayer_test-0.2.0.dist-info/RECORD +0 -55
- gltest/plugin_config.py +0 -42
- gltest/plugin_hooks.py +0 -51
- tests/artifact/test_contract_definition.py +0 -347
- tests/plugin/test_plugin_hooks.py +0 -78
- {genlayer_test-0.2.0.dist-info → genlayer_test-0.3.1.dist-info}/WHEEL +0 -0
- {genlayer_test-0.2.0.dist-info → genlayer_test-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {genlayer_test-0.2.0.dist-info → genlayer_test-0.3.1.dist-info}/top_level.txt +0 -0
- /tests/{plugin/conftest.py → conftest.py} +0 -0
- /tests/{artifact → gltest/artifact}/contracts/duplicate_ic_contract_1.py +0 -0
- /tests/{artifact → gltest/artifact}/contracts/duplicate_ic_contract_2.py +0 -0
- /tests/{artifact → gltest/artifact}/contracts/not_ic_contract.py +0 -0
- /tests/{assertions → gltest/assertions}/test_assertions.py +0 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from gltest_cli.logging import logger
|
3
|
+
from gltest_cli.config.user import (
|
4
|
+
user_config_exists,
|
5
|
+
load_user_config,
|
6
|
+
get_default_user_config,
|
7
|
+
)
|
8
|
+
from gltest_cli.config.general import (
|
9
|
+
get_general_config,
|
10
|
+
)
|
11
|
+
from gltest_cli.config.types import PluginConfig
|
12
|
+
|
13
|
+
|
14
|
+
def pytest_addoption(parser):
|
15
|
+
group = parser.getgroup("gltest")
|
16
|
+
group.addoption(
|
17
|
+
"--contracts-dir",
|
18
|
+
action="store",
|
19
|
+
default=None,
|
20
|
+
help="Path to directory containing contract files",
|
21
|
+
)
|
22
|
+
|
23
|
+
group.addoption(
|
24
|
+
"--default-wait-interval",
|
25
|
+
action="store",
|
26
|
+
default=10000,
|
27
|
+
help="Default interval (ms) between transaction receipt checks",
|
28
|
+
)
|
29
|
+
|
30
|
+
group.addoption(
|
31
|
+
"--default-wait-retries",
|
32
|
+
action="store",
|
33
|
+
default=15,
|
34
|
+
help="Default number of retries for transaction receipt checks",
|
35
|
+
)
|
36
|
+
|
37
|
+
group.addoption(
|
38
|
+
"--rpc-url",
|
39
|
+
action="store",
|
40
|
+
default=None,
|
41
|
+
help="RPC endpoint URL for the GenLayer network",
|
42
|
+
)
|
43
|
+
|
44
|
+
group.addoption(
|
45
|
+
"--network",
|
46
|
+
action="store",
|
47
|
+
default=None,
|
48
|
+
help="Target network (defaults to 'localnet' if no config file)",
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def pytest_configure(config):
|
53
|
+
general_config = get_general_config()
|
54
|
+
|
55
|
+
# Handle user config from gltest.config.yaml
|
56
|
+
if not user_config_exists():
|
57
|
+
logger.warning(
|
58
|
+
"File `gltest.config.yaml` not found in the current directory, using default config"
|
59
|
+
)
|
60
|
+
logger.info("Create a `gltest.config.yaml` file to manage multiple networks")
|
61
|
+
user_config = get_default_user_config()
|
62
|
+
else:
|
63
|
+
logger.info(
|
64
|
+
"File `gltest.config.yaml` found in the current directory, using it"
|
65
|
+
)
|
66
|
+
user_config = load_user_config("gltest.config.yaml")
|
67
|
+
|
68
|
+
general_config.user_config = user_config
|
69
|
+
|
70
|
+
# Handle plugin config from command line
|
71
|
+
contracts_dir = config.getoption("--contracts-dir")
|
72
|
+
default_wait_interval = config.getoption("--default-wait-interval")
|
73
|
+
default_wait_retries = config.getoption("--default-wait-retries")
|
74
|
+
rpc_url = config.getoption("--rpc-url")
|
75
|
+
network = config.getoption("--network")
|
76
|
+
|
77
|
+
plugin_config = PluginConfig()
|
78
|
+
plugin_config.contracts_dir = (
|
79
|
+
Path(contracts_dir) if contracts_dir is not None else None
|
80
|
+
)
|
81
|
+
plugin_config.default_wait_interval = int(default_wait_interval)
|
82
|
+
plugin_config.default_wait_retries = int(default_wait_retries)
|
83
|
+
plugin_config.rpc_url = rpc_url
|
84
|
+
plugin_config.network_name = network
|
85
|
+
|
86
|
+
general_config.plugin_config = plugin_config
|
87
|
+
|
88
|
+
|
89
|
+
def pytest_sessionstart(session):
|
90
|
+
general_config = get_general_config()
|
91
|
+
logger.info("Using the following configuration:")
|
92
|
+
logger.info(f" RPC URL: {general_config.get_rpc_url()}")
|
93
|
+
logger.info(f" Selected Network: {general_config.get_network_name()}")
|
94
|
+
logger.info(
|
95
|
+
f" Available networks: {list(general_config.user_config.networks.keys())}"
|
96
|
+
)
|
97
|
+
logger.info(f" Contracts directory: {general_config.get_contracts_dir()}")
|
98
|
+
logger.info(f" Environment: {general_config.user_config.environment}")
|
99
|
+
logger.info(
|
100
|
+
f" Default wait interval: {general_config.get_default_wait_interval()} ms"
|
101
|
+
)
|
102
|
+
logger.info(f" Default wait retries: {general_config.get_default_wait_retries()}")
|
@@ -0,0 +1,137 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Dict, List, Optional
|
5
|
+
from genlayer_py.chains import localnet, testnet_asimov
|
6
|
+
from genlayer_py.types import GenLayerChain
|
7
|
+
|
8
|
+
|
9
|
+
class NetworkConfig(str, Enum):
|
10
|
+
LOCALNET = "localnet"
|
11
|
+
TESTNET_ASIMOV = "testnet_asimov"
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class PluginConfig:
|
16
|
+
contracts_dir: Optional[Path] = None
|
17
|
+
rpc_url: Optional[str] = None
|
18
|
+
default_wait_interval: Optional[int] = None
|
19
|
+
default_wait_retries: Optional[int] = None
|
20
|
+
network_name: Optional[str] = None
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class NetworkConfigData:
|
25
|
+
id: Optional[int] = None
|
26
|
+
url: Optional[str] = None
|
27
|
+
accounts: Optional[List[str]] = None
|
28
|
+
from_account: Optional[str] = None
|
29
|
+
|
30
|
+
def __post_init__(self):
|
31
|
+
if self.id is not None and not isinstance(self.id, int):
|
32
|
+
raise ValueError("id must be an integer")
|
33
|
+
if self.url is not None and not isinstance(self.url, str):
|
34
|
+
raise ValueError("url must be a string")
|
35
|
+
if self.accounts is not None:
|
36
|
+
if not isinstance(self.accounts, list):
|
37
|
+
raise ValueError("accounts must be a list")
|
38
|
+
if not all(isinstance(acc, str) for acc in self.accounts):
|
39
|
+
raise ValueError("accounts must be strings")
|
40
|
+
if self.from_account is not None and not isinstance(self.from_account, str):
|
41
|
+
raise ValueError("from_account must be a string")
|
42
|
+
|
43
|
+
|
44
|
+
@dataclass
|
45
|
+
class PathConfig:
|
46
|
+
contracts: Optional[Path] = None
|
47
|
+
|
48
|
+
def __post_init__(self):
|
49
|
+
if self.contracts is not None and not isinstance(self.contracts, (str, Path)):
|
50
|
+
raise ValueError("contracts must be a string or Path")
|
51
|
+
|
52
|
+
|
53
|
+
@dataclass
|
54
|
+
class UserConfig:
|
55
|
+
networks: Dict[str, NetworkConfigData] = field(default_factory=dict)
|
56
|
+
paths: PathConfig = field(default_factory=PathConfig)
|
57
|
+
environment: Optional[str] = None
|
58
|
+
default_network: Optional[str] = None
|
59
|
+
|
60
|
+
def __post_init__(self):
|
61
|
+
if not isinstance(self.networks, dict):
|
62
|
+
raise ValueError("networks must be a dictionary")
|
63
|
+
|
64
|
+
if not isinstance(self.paths, PathConfig):
|
65
|
+
raise ValueError("paths must be a PathConfig instance")
|
66
|
+
|
67
|
+
if self.environment is not None and not isinstance(self.environment, str):
|
68
|
+
raise ValueError("environment must be a string")
|
69
|
+
|
70
|
+
if self.default_network is not None and not isinstance(
|
71
|
+
self.default_network, str
|
72
|
+
):
|
73
|
+
raise ValueError("default_network must be a string")
|
74
|
+
|
75
|
+
# Validate network configurations
|
76
|
+
for name, network_config in self.networks.items():
|
77
|
+
if not isinstance(network_config, NetworkConfigData):
|
78
|
+
raise ValueError(f"network {name} must be a NetworkConfigData instance")
|
79
|
+
|
80
|
+
|
81
|
+
@dataclass
|
82
|
+
class GeneralConfig:
|
83
|
+
user_config: UserConfig = field(default_factory=UserConfig)
|
84
|
+
plugin_config: PluginConfig = field(default_factory=PluginConfig)
|
85
|
+
|
86
|
+
def get_contracts_dir(self) -> Path:
|
87
|
+
if self.plugin_config.contracts_dir is not None:
|
88
|
+
return self.plugin_config.contracts_dir
|
89
|
+
return self.user_config.paths.contracts
|
90
|
+
|
91
|
+
def set_contracts_dir(self, contracts_dir: Path):
|
92
|
+
self.plugin_config.contracts_dir = contracts_dir
|
93
|
+
|
94
|
+
def get_rpc_url(self) -> str:
|
95
|
+
if self.plugin_config.rpc_url is not None:
|
96
|
+
return self.plugin_config.rpc_url
|
97
|
+
network_name = self.get_network_name()
|
98
|
+
return self.user_config.networks[network_name].url
|
99
|
+
|
100
|
+
def get_default_account_key(self, network_name: Optional[str] = None) -> str:
|
101
|
+
if network_name is not None:
|
102
|
+
return self.user_config.networks[network_name].from_account
|
103
|
+
return self.user_config.networks[self.user_config.default_network].from_account
|
104
|
+
|
105
|
+
def get_accounts_keys(self, network_name: Optional[str] = None) -> List[str]:
|
106
|
+
if network_name is not None:
|
107
|
+
return self.user_config.networks[network_name].accounts
|
108
|
+
return self.user_config.networks[self.user_config.default_network].accounts
|
109
|
+
|
110
|
+
def get_chain(self) -> GenLayerChain:
|
111
|
+
chain_map_by_id = {
|
112
|
+
61999: localnet,
|
113
|
+
4221: testnet_asimov,
|
114
|
+
}
|
115
|
+
network_name = self.get_network_name()
|
116
|
+
network_id = self.user_config.networks[network_name].id
|
117
|
+
if network_id not in chain_map_by_id:
|
118
|
+
known = ", ".join(map(str, chain_map_by_id.keys()))
|
119
|
+
raise ValueError(
|
120
|
+
f"Unknown network: {network_name}, possible values: {known}"
|
121
|
+
)
|
122
|
+
return chain_map_by_id[network_id]
|
123
|
+
|
124
|
+
def get_default_wait_interval(self) -> int:
|
125
|
+
if self.plugin_config.default_wait_interval is not None:
|
126
|
+
return self.plugin_config.default_wait_interval
|
127
|
+
raise ValueError("default_wait_interval is not set")
|
128
|
+
|
129
|
+
def get_default_wait_retries(self) -> int:
|
130
|
+
if self.plugin_config.default_wait_retries is not None:
|
131
|
+
return self.plugin_config.default_wait_retries
|
132
|
+
raise ValueError("default_wait_retries is not set")
|
133
|
+
|
134
|
+
def get_network_name(self) -> str:
|
135
|
+
if self.plugin_config.network_name is not None:
|
136
|
+
return self.plugin_config.network_name
|
137
|
+
return self.user_config.default_network
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import os
|
2
|
+
import yaml
|
3
|
+
import re
|
4
|
+
from dotenv import load_dotenv
|
5
|
+
from pathlib import Path
|
6
|
+
from functools import lru_cache
|
7
|
+
from gltest.glchain.account import create_accounts
|
8
|
+
from gltest_cli.config.constants import (
|
9
|
+
GLTEST_CONFIG_FILE,
|
10
|
+
DEFAULT_NETWORK,
|
11
|
+
DEFAULT_RPC_URL,
|
12
|
+
DEFAULT_ENVIRONMENT,
|
13
|
+
DEFAULT_CONTRACTS_DIR,
|
14
|
+
DEFAULT_NETWORK_ID,
|
15
|
+
)
|
16
|
+
from gltest_cli.config.types import UserConfig, NetworkConfigData, PathConfig
|
17
|
+
|
18
|
+
VALID_ROOT_KEYS = ["networks", "paths", "environment"]
|
19
|
+
VALID_NETWORK_KEYS = ["id", "url", "accounts", "from"]
|
20
|
+
VALID_PATHS_KEYS = ["contracts"]
|
21
|
+
|
22
|
+
|
23
|
+
@lru_cache(maxsize=1)
|
24
|
+
def get_default_user_config() -> UserConfig:
|
25
|
+
accounts = create_accounts(n_accounts=10)
|
26
|
+
accounts_private_keys = [account.key.hex() for account in accounts]
|
27
|
+
|
28
|
+
return UserConfig(
|
29
|
+
networks={
|
30
|
+
DEFAULT_NETWORK: NetworkConfigData(
|
31
|
+
id=DEFAULT_NETWORK_ID,
|
32
|
+
url=DEFAULT_RPC_URL,
|
33
|
+
accounts=accounts_private_keys,
|
34
|
+
from_account=accounts_private_keys[0],
|
35
|
+
),
|
36
|
+
},
|
37
|
+
paths=PathConfig(contracts=DEFAULT_CONTRACTS_DIR),
|
38
|
+
environment=DEFAULT_ENVIRONMENT,
|
39
|
+
default_network=DEFAULT_NETWORK,
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def resolve_env_vars(obj):
|
44
|
+
if isinstance(obj, str):
|
45
|
+
return re.sub(
|
46
|
+
r"\${(\w+)}",
|
47
|
+
lambda m: os.getenv(m.group(1), f"<UNSET:{m.group(1)}>"),
|
48
|
+
obj,
|
49
|
+
)
|
50
|
+
elif isinstance(obj, dict):
|
51
|
+
return {k: resolve_env_vars(v) for k, v in obj.items()}
|
52
|
+
elif isinstance(obj, list):
|
53
|
+
return [resolve_env_vars(i) for i in obj]
|
54
|
+
return obj
|
55
|
+
|
56
|
+
|
57
|
+
def validate_network_config(network_name: str, network_config: dict):
|
58
|
+
if not isinstance(network_config, dict):
|
59
|
+
raise ValueError(f"network {network_name} must be a dictionary")
|
60
|
+
|
61
|
+
for key in network_config:
|
62
|
+
if key not in VALID_NETWORK_KEYS:
|
63
|
+
raise ValueError(
|
64
|
+
f"Invalid network key: {key}, valid keys are: {VALID_NETWORK_KEYS}"
|
65
|
+
)
|
66
|
+
|
67
|
+
if "id" in network_config and not isinstance(network_config["id"], int):
|
68
|
+
raise ValueError(f"network {network_name} id must be an integer")
|
69
|
+
|
70
|
+
if "url" in network_config and not isinstance(network_config["url"], str):
|
71
|
+
raise ValueError(f"network {network_name} url must be a string")
|
72
|
+
|
73
|
+
if "accounts" in network_config and not isinstance(
|
74
|
+
network_config["accounts"], list
|
75
|
+
):
|
76
|
+
raise ValueError(f"network {network_name} accounts must be a list")
|
77
|
+
if "accounts" in network_config and not all(
|
78
|
+
isinstance(acc, str) for acc in network_config["accounts"]
|
79
|
+
):
|
80
|
+
raise ValueError(f"network {network_name} accounts must be strings")
|
81
|
+
|
82
|
+
if "from" in network_config and not isinstance(network_config["from"], str):
|
83
|
+
raise ValueError(f"network {network_name} from must be a string")
|
84
|
+
|
85
|
+
# For non-default networks, url and accounts are required
|
86
|
+
if network_name != DEFAULT_NETWORK:
|
87
|
+
if "id" not in network_config:
|
88
|
+
raise ValueError(f"network {network_name} must have an id")
|
89
|
+
if "url" not in network_config:
|
90
|
+
raise ValueError(f"network {network_name} must have a url")
|
91
|
+
if "accounts" not in network_config:
|
92
|
+
raise ValueError(f"network {network_name} must have accounts")
|
93
|
+
|
94
|
+
|
95
|
+
def validate_raw_user_config(config: dict):
|
96
|
+
# Validate root keys
|
97
|
+
if not all(key in VALID_ROOT_KEYS for key in config):
|
98
|
+
raise ValueError(
|
99
|
+
f"Invalid configuration keys. Valid keys are: {VALID_ROOT_KEYS}"
|
100
|
+
)
|
101
|
+
|
102
|
+
# Validate networks
|
103
|
+
if "networks" in config:
|
104
|
+
networks = config["networks"]
|
105
|
+
if not isinstance(networks, dict):
|
106
|
+
raise ValueError("networks must be a dictionary")
|
107
|
+
|
108
|
+
default_network = networks.get("default", DEFAULT_NETWORK)
|
109
|
+
if default_network != DEFAULT_NETWORK and default_network not in networks:
|
110
|
+
raise ValueError(f"default network {default_network} not found in networks")
|
111
|
+
|
112
|
+
for name, network_config in networks.items():
|
113
|
+
if name == "default" or (
|
114
|
+
name == DEFAULT_NETWORK and network_config is None
|
115
|
+
):
|
116
|
+
continue
|
117
|
+
validate_network_config(name, network_config)
|
118
|
+
|
119
|
+
# Validate paths
|
120
|
+
if "paths" in config:
|
121
|
+
if not isinstance(config["paths"], dict):
|
122
|
+
raise ValueError("paths must be a dictionary")
|
123
|
+
if not all(key in VALID_PATHS_KEYS for key in config["paths"]):
|
124
|
+
raise ValueError(f"Invalid path keys. Valid keys are: {VALID_PATHS_KEYS}")
|
125
|
+
|
126
|
+
# Validate environment
|
127
|
+
if "environment" in config and not isinstance(config["environment"], str):
|
128
|
+
raise ValueError("environment must be a string")
|
129
|
+
|
130
|
+
|
131
|
+
def load_user_config(path: str) -> UserConfig:
|
132
|
+
with open(path, "r") as f:
|
133
|
+
raw_config = yaml.safe_load(f) or {}
|
134
|
+
|
135
|
+
validate_raw_user_config(raw_config)
|
136
|
+
load_dotenv(
|
137
|
+
dotenv_path=raw_config.get("environment", DEFAULT_ENVIRONMENT), override=True
|
138
|
+
)
|
139
|
+
resolved_config = resolve_env_vars(raw_config)
|
140
|
+
user_config = transform_raw_to_user_config_with_defaults(resolved_config)
|
141
|
+
return user_config
|
142
|
+
|
143
|
+
|
144
|
+
def transform_raw_to_user_config_with_defaults(raw_config: dict) -> UserConfig:
|
145
|
+
networks_config, user_default_network = _get_overridden_networks(raw_config)
|
146
|
+
return UserConfig(
|
147
|
+
networks=networks_config,
|
148
|
+
paths=_get_overridden_paths(raw_config),
|
149
|
+
environment=_get_overridden_environment(raw_config),
|
150
|
+
default_network=user_default_network,
|
151
|
+
)
|
152
|
+
|
153
|
+
|
154
|
+
def _get_overridden_networks(raw_config: dict) -> tuple[dict, str]:
|
155
|
+
default_config = get_default_user_config()
|
156
|
+
if "networks" not in raw_config:
|
157
|
+
return default_config.networks, default_config.default_network
|
158
|
+
|
159
|
+
networks = dict(raw_config["networks"])
|
160
|
+
user_default_network = networks.pop("default")
|
161
|
+
if user_default_network is None and DEFAULT_NETWORK in set(networks.keys()):
|
162
|
+
user_default_network = DEFAULT_NETWORK
|
163
|
+
|
164
|
+
if user_default_network is None:
|
165
|
+
raise ValueError(
|
166
|
+
"'networks.default' is required in config since you don't have 'localnet' network in 'networks'"
|
167
|
+
)
|
168
|
+
|
169
|
+
networks_config = {}
|
170
|
+
for network_name, network_config in networks.items():
|
171
|
+
if network_name == DEFAULT_NETWORK:
|
172
|
+
networks_config[network_name] = default_config.networks[DEFAULT_NETWORK]
|
173
|
+
if network_config is None:
|
174
|
+
continue
|
175
|
+
|
176
|
+
if "url" in network_config:
|
177
|
+
networks_config[network_name].url = network_config["url"]
|
178
|
+
if "accounts" in network_config:
|
179
|
+
networks_config[network_name].accounts = network_config["accounts"]
|
180
|
+
networks_config[network_name].from_account = network_config["accounts"][
|
181
|
+
0
|
182
|
+
]
|
183
|
+
if "from" in network_config:
|
184
|
+
networks_config[network_name].from_account = network_config["from"]
|
185
|
+
continue
|
186
|
+
|
187
|
+
url = network_config["url"]
|
188
|
+
accounts = network_config["accounts"]
|
189
|
+
from_account = network_config.get("from", accounts[0])
|
190
|
+
network_id = network_config.get("id")
|
191
|
+
|
192
|
+
networks_config[network_name] = NetworkConfigData(
|
193
|
+
id=network_id,
|
194
|
+
url=url,
|
195
|
+
accounts=accounts,
|
196
|
+
from_account=from_account,
|
197
|
+
)
|
198
|
+
return networks_config, user_default_network
|
199
|
+
|
200
|
+
|
201
|
+
def _get_overridden_environment(raw_config: dict) -> str:
|
202
|
+
default_config = get_default_user_config()
|
203
|
+
if "environment" in raw_config:
|
204
|
+
return raw_config["environment"]
|
205
|
+
return default_config.environment
|
206
|
+
|
207
|
+
|
208
|
+
def _get_overridden_paths(raw_config: dict) -> PathConfig:
|
209
|
+
default_config = get_default_user_config()
|
210
|
+
if "paths" in raw_config:
|
211
|
+
return PathConfig(
|
212
|
+
contracts=Path(
|
213
|
+
raw_config.get("paths", {}).get("contracts", DEFAULT_CONTRACTS_DIR)
|
214
|
+
)
|
215
|
+
)
|
216
|
+
return default_config.paths
|
217
|
+
|
218
|
+
|
219
|
+
def user_config_exists() -> bool:
|
220
|
+
return any(
|
221
|
+
p.name == GLTEST_CONFIG_FILE for p in Path.cwd().iterdir() if p.is_file()
|
222
|
+
)
|
gltest_cli/logging.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import logging
|
2
|
+
from colorama import Fore, Style, init
|
3
|
+
|
4
|
+
|
5
|
+
init()
|
6
|
+
|
7
|
+
|
8
|
+
class ColoredFormatter(logging.Formatter):
|
9
|
+
"""Custom formatter that adds colors to the log levels"""
|
10
|
+
|
11
|
+
COLORS = {
|
12
|
+
"DEBUG": Fore.BLUE,
|
13
|
+
"INFO": Fore.GREEN,
|
14
|
+
"WARNING": Fore.YELLOW,
|
15
|
+
"ERROR": Fore.RED,
|
16
|
+
"CRITICAL": Fore.RED + Style.BRIGHT,
|
17
|
+
}
|
18
|
+
|
19
|
+
def format(self, record):
|
20
|
+
levelname_plain = record.levelname
|
21
|
+
if levelname_plain in self.COLORS:
|
22
|
+
colored = (
|
23
|
+
f"{self.COLORS[levelname_plain]}{levelname_plain}{Style.RESET_ALL}"
|
24
|
+
)
|
25
|
+
record.levelname = colored
|
26
|
+
formatted = super().format(record)
|
27
|
+
record.levelname = levelname_plain
|
28
|
+
return formatted
|
29
|
+
return super().format(record)
|
30
|
+
|
31
|
+
|
32
|
+
def setup_logger():
|
33
|
+
logger = logging.getLogger("gltest")
|
34
|
+
logger.setLevel(logging.DEBUG)
|
35
|
+
|
36
|
+
if logger.handlers:
|
37
|
+
return logger
|
38
|
+
|
39
|
+
console_handler = logging.StreamHandler()
|
40
|
+
console_handler.setLevel(logging.DEBUG)
|
41
|
+
|
42
|
+
formatter = ColoredFormatter("%(levelname)s: %(message)s")
|
43
|
+
console_handler.setFormatter(formatter)
|
44
|
+
|
45
|
+
logger.addHandler(console_handler)
|
46
|
+
return logger
|
47
|
+
|
48
|
+
|
49
|
+
logger = setup_logger()
|
50
|
+
|
51
|
+
__all__ = ["logger"]
|
tests/__init__.py
ADDED
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from gltest import get_contract_factory,
|
1
|
+
from gltest import get_contract_factory, get_default_account, create_account
|
2
2
|
from gltest.assertions import tx_execution_succeeded
|
3
3
|
|
4
4
|
TOKEN_TOTAL_SUPPLY = 1000
|
@@ -7,7 +7,7 @@ TRANSFER_AMOUNT = 100
|
|
7
7
|
|
8
8
|
def test_llm_erc20():
|
9
9
|
# Account Setup
|
10
|
-
from_account_a =
|
10
|
+
from_account_a = get_default_account()
|
11
11
|
from_account_b = create_account()
|
12
12
|
|
13
13
|
# Deploy Contract
|
@@ -1,5 +1,7 @@
|
|
1
|
-
from gltest import get_contract_factory, create_account
|
1
|
+
from gltest import get_contract_factory, create_account, get_accounts
|
2
2
|
from gltest.assertions import tx_execution_succeeded
|
3
|
+
from gltest_cli.config.general import get_general_config
|
4
|
+
from genlayer_py.chains import testnet_asimov
|
3
5
|
|
4
6
|
|
5
7
|
def test_multi_read_erc20():
|
@@ -15,9 +17,17 @@ def test_multi_read_erc20():
|
|
15
17
|
|
16
18
|
This test demonstrates the integration contract to contract reads
|
17
19
|
"""
|
20
|
+
general_config = get_general_config()
|
21
|
+
chain = general_config.get_chain()
|
22
|
+
|
18
23
|
TOKEN_TOTAL_SUPPLY = 1000
|
19
|
-
|
20
|
-
|
24
|
+
|
25
|
+
if chain.id == testnet_asimov.id:
|
26
|
+
from_account_doge = get_accounts()[0]
|
27
|
+
from_account_shiba = get_accounts()[1]
|
28
|
+
else:
|
29
|
+
from_account_doge = create_account()
|
30
|
+
from_account_shiba = create_account()
|
21
31
|
|
22
32
|
# LLM ERC20
|
23
33
|
llm_erc20_factory = get_contract_factory("LlmErc20")
|
@@ -1,5 +1,7 @@
|
|
1
|
-
from gltest import get_contract_factory, create_account
|
1
|
+
from gltest import get_contract_factory, create_account, get_accounts
|
2
2
|
from gltest.assertions import tx_execution_succeeded
|
3
|
+
from gltest_cli.config.general import get_general_config
|
4
|
+
from genlayer_py.chains import testnet_asimov
|
3
5
|
|
4
6
|
|
5
7
|
def test_multi_tenant_storage():
|
@@ -15,8 +17,15 @@ def test_multi_tenant_storage():
|
|
15
17
|
|
16
18
|
This test demonstrates contract-to-contract interactions and multi-tenant data management.
|
17
19
|
"""
|
18
|
-
|
19
|
-
|
20
|
+
general_config = get_general_config()
|
21
|
+
chain = general_config.get_chain()
|
22
|
+
|
23
|
+
if chain.id == testnet_asimov.id:
|
24
|
+
user_account_a = get_accounts()[0]
|
25
|
+
user_account_b = get_accounts()[1]
|
26
|
+
else:
|
27
|
+
user_account_a = create_account()
|
28
|
+
user_account_b = create_account()
|
20
29
|
|
21
30
|
# Storage Contracts
|
22
31
|
storage_factory = get_contract_factory("Storage")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from gltest import get_contract_factory,
|
1
|
+
from gltest import get_contract_factory, get_default_account
|
2
2
|
|
3
3
|
|
4
4
|
def test_read_erc20():
|
@@ -32,6 +32,6 @@ def test_read_erc20():
|
|
32
32
|
|
33
33
|
# check balance
|
34
34
|
contract_state = read_erc20_contract.get_balance_of(
|
35
|
-
args=[
|
35
|
+
args=[get_default_account().address]
|
36
36
|
)
|
37
37
|
assert contract_state == TOKEN_TOTAL_SUPPLY
|
@@ -8,14 +8,16 @@ UPDATED_STATE = "b"
|
|
8
8
|
|
9
9
|
def test_storage():
|
10
10
|
factory = get_contract_factory("Storage")
|
11
|
-
contract = factory.deploy(args=[INITIAL_STATE])
|
11
|
+
contract = factory.deploy(args=[INITIAL_STATE], wait_retries=40)
|
12
12
|
|
13
13
|
# Get initial state
|
14
14
|
contract_state_1 = contract.get_storage(args=[])
|
15
15
|
assert contract_state_1 == INITIAL_STATE
|
16
16
|
|
17
17
|
# Update State
|
18
|
-
transaction_response_call_1 = contract.update_storage(
|
18
|
+
transaction_response_call_1 = contract.update_storage(
|
19
|
+
args=[UPDATED_STATE], wait_retries=40
|
20
|
+
)
|
19
21
|
assert tx_execution_succeeded(transaction_response_call_1)
|
20
22
|
|
21
23
|
# Get Updated State
|
@@ -1,5 +1,12 @@
|
|
1
|
-
from gltest import
|
1
|
+
from gltest import (
|
2
|
+
get_contract_factory,
|
3
|
+
get_default_account,
|
4
|
+
create_account,
|
5
|
+
get_accounts,
|
6
|
+
)
|
2
7
|
from gltest.assertions import tx_execution_succeeded
|
8
|
+
from gltest_cli.config.general import get_general_config
|
9
|
+
from genlayer_py.chains import testnet_asimov
|
3
10
|
|
4
11
|
|
5
12
|
INITIAL_STATE_USER_A = "user_a_initial_state"
|
@@ -9,9 +16,16 @@ UPDATED_STATE_USER_B = "user_b_updated_state"
|
|
9
16
|
|
10
17
|
|
11
18
|
def test_user_storage():
|
19
|
+
general_config = get_general_config()
|
20
|
+
chain = general_config.get_chain()
|
21
|
+
|
12
22
|
# Account Setup
|
13
|
-
|
14
|
-
|
23
|
+
if chain.id == testnet_asimov.id:
|
24
|
+
from_account_a = get_accounts()[0]
|
25
|
+
from_account_b = get_accounts()[1]
|
26
|
+
else:
|
27
|
+
from_account_a = get_default_account()
|
28
|
+
from_account_b = create_account()
|
15
29
|
|
16
30
|
factory = get_contract_factory("UserStorage")
|
17
31
|
contract = factory.deploy()
|
tests/gltest/__init__.py
ADDED
File without changes
|
File without changes
|