genlayer-test 0.1.2__py3-none-any.whl → 0.2.0__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.
@@ -1,18 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: genlayer-test
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: GenLayer Testing Suite
5
5
  Author: GenLayer
6
6
  License-Expression: MIT
7
7
  Classifier: Development Status :: 4 - Beta
8
8
  Classifier: Intended Audience :: Developers
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.8
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
14
12
  Classifier: Topic :: Software Development :: Testing
15
- Requires-Python: >=3.8
13
+ Requires-Python: >=3.12
16
14
  Description-Content-Type: text/markdown
17
15
  License-File: LICENSE
18
16
  Requires-Dist: pytest
@@ -162,7 +160,7 @@ Before diving into the examples, let's understand the basic project structure:
162
160
  ```
163
161
  genlayer-example/
164
162
  ├── contracts/ # Contract definitions
165
- │ └── storage.gpy # Example storage contract
163
+ │ └── storage.py # Example storage contract
166
164
  └── test/ # Test files
167
165
  └── test_contract.py # Contract test cases
168
166
  ```
@@ -279,6 +277,44 @@ def test_write_methods():
279
277
  assert contract.get_storage() == "new_value"
280
278
  ```
281
279
 
280
+ ### Assertions
281
+
282
+ The GenLayer Testing Suite provides powerful assertion functions to validate transaction results and their output:
283
+
284
+ #### Basic Transaction Assertions
285
+
286
+ ```python
287
+ from gltest.assertions import tx_execution_succeeded, tx_execution_failed
288
+
289
+ # Basic success/failure checks
290
+ assert tx_execution_succeeded(tx_receipt)
291
+ assert tx_execution_failed(tx_receipt) # Opposite of tx_execution_succeeded
292
+ ```
293
+
294
+ #### Advanced Output Matching
295
+
296
+ You can match specific patterns in the transaction's stdout and stderr output using regex patterns, similar to pytest's `match` parameter:
297
+
298
+ ```python
299
+ # Simple string matching
300
+ assert tx_execution_succeeded(tx_receipt, match_std_out="Process completed")
301
+ assert tx_execution_failed(tx_receipt, match_std_err="Warning: deprecated")
302
+
303
+ # Regex pattern matching
304
+ assert tx_execution_succeeded(tx_receipt, match_std_out=r".*code \d+")
305
+ assert tx_execution_failed(tx_receipt, match_std_err=r"Method.*failed")
306
+ ```
307
+
308
+ #### Assertion Function Parameters
309
+
310
+ Both `tx_execution_succeeded` and `tx_execution_failed` accept the following parameters:
311
+
312
+ - `result`: The transaction result object from contract method calls
313
+ - `match_std_out` (optional): String or regex pattern to match in stdout
314
+ - `match_std_err` (optional): String or regex pattern to match in stderr
315
+
316
+ **Network Compatibility**: The stdout/stderr matching feature (`match_std_out` and `match_std_err` parameters) is only available when running on **studionet** and **localnet**. These features are not supported on testnet.
317
+
282
318
  For more example contracts, check out the [contracts directory](tests/examples/contracts) which contains various sample contracts demonstrating different features and use cases.
283
319
 
284
320
  ## 📝 Best Practices
@@ -354,7 +390,7 @@ For more example contracts, check out the [contracts directory](tests/examples/c
354
390
  # Default structure
355
391
  your_project/
356
392
  ├── contracts/ # Default contracts directory
357
- │ └── my_contract.gpy # Your contract file
393
+ │ └── my_contract.py # Your contract file
358
394
  └── tests/
359
395
  └── test_contract.py # Your test file
360
396
 
@@ -366,7 +402,8 @@ For more example contracts, check out the [contracts directory](tests/examples/c
366
402
  - **Problem**: Contracts aren't being recognized or loaded properly.
367
403
  - **Solution**: Follow the correct naming and structure conventions:
368
404
  ```python
369
- # Correct file: contracts/my_contract.gpy
405
+ # Correct file: contracts/my_contract.py
406
+
370
407
  # Correct structure:
371
408
  from genlayer import *
372
409
 
@@ -374,7 +411,7 @@ For more example contracts, check out the [contracts directory](tests/examples/c
374
411
  # Contract code here
375
412
  pass
376
413
 
377
- # Incorrect file: contracts/my_contract.py # Wrong extension
414
+
378
415
  # Incorrect structure:
379
416
  class MyContract: # Missing gl.Contract inheritance
380
417
  pass
@@ -385,7 +422,7 @@ For more example contracts, check out the [contracts directory](tests/examples/c
385
422
  - **Solution**: Verify your environment:
386
423
  ```bash
387
424
  # Check Python version
388
- python --version # Should be >= 3.8
425
+ python --version # Should be >= 3.12
389
426
 
390
427
  # Check GenLayer Studio status
391
428
  docker ps # Should show GenLayer Studio running
@@ -1,23 +1,25 @@
1
- genlayer_test-0.1.2.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
1
+ genlayer_test-0.2.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
2
2
  gltest/__init__.py,sha256=AK_YfRvwlhrOheOelUG8qIRG17on0-nFCF747dopg2w,332
3
- gltest/assertions.py,sha256=dBjiK5-LuxrrktkNDP7n-t6i43UNCL_cq7M4BLqRyFk,678
3
+ gltest/assertions.py,sha256=0dEk0VxcHK4I7GZPHxJmz-2jaA60V499gOSR74rZbfM,1748
4
4
  gltest/exceptions.py,sha256=deJPmrTe5gF33qkkKF2IVJY7lc_knI7Ql3N7jZ8aLZs,510
5
5
  gltest/plugin_config.py,sha256=8Z97RtEJ89OcRbki_oRuBBVct_q56BFmKvthan1y9Y4,840
6
6
  gltest/plugin_hooks.py,sha256=py1rzIR9QSsFOt8SEePPL96e-8DeiPFxvcPZurRlExM,1436
7
7
  gltest/types.py,sha256=BODmwTr2gAUEiO9FjiuTiWwuKvXgo4xZWstQWNUfnlw,156
8
- gltest/artifacts/__init__.py,sha256=qiJN5F1cVh_3fmxZeDFznoABxAdLhLqBfUUKwqXqvgo,87
9
- gltest/artifacts/contract.py,sha256=CBRfUTltKv2tPy--LcmVbP_4hbXYt9VrunvJlMzNd5s,3566
8
+ gltest/artifacts/__init__.py,sha256=qTt3TE19gVNWnQLUlt5aDe4nNvJ2YJ1jzDkMmYIsCG0,194
9
+ gltest/artifacts/contract.py,sha256=jqdsJD9B4aUTpR4EODHaag5j-IvdWm3Ac4ektbpTxFo,6152
10
10
  gltest/glchain/__init__.py,sha256=X-mEbREoAOe9K4n74C55gCiXH4wItzY5HTJcg3_F3mI,412
11
11
  gltest/glchain/account.py,sha256=ZxYsfbtBXKVC5vV4pko3yyL6lhPljqIb68NgIgvInSc,403
12
12
  gltest/glchain/client.py,sha256=q04LIQy5SCIrYZflZiTapfeQ-AaSTa0w369ehnVbJLM,532
13
- gltest/glchain/contract.py,sha256=1pd4eAQlHXjnRF7hBvnoR-9f-HORUaYKVyV5P42io4o,8747
13
+ gltest/glchain/contract.py,sha256=aNS2Wm93BG4kug4iyWembBDaNb2mYCTJOTfDzMPoiRE,10042
14
14
  gltest/helpers/__init__.py,sha256=I7HiTu_H7_hP65zY6Wl02r-5eAMr2eZvqBVmusuQLX4,180
15
15
  gltest/helpers/fixture_snapshot.py,sha256=DWLTsMbTnfhpv0_7_gkJpDKX4hJx-tlruX7x3FWL6UI,2073
16
16
  gltest/helpers/take_snapshot.py,sha256=eXqEKXM2hcox3pLGIcNddobU8zXPQvD-Iwf87eHqW2s,1276
17
17
  gltest_cli/main.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
18
- tests/artifact/test_contract_definition.py,sha256=8X829MsNfEObtIMmotk6nRVLDEp5KfJE6zrAK4IPfCc,3323
18
+ tests/artifact/test_contract_definition.py,sha256=vr3guuJOvsQorUyv_ahbNvUwfDi6cq_NKAbQMATMp1g,13803
19
+ tests/artifact/contracts/duplicate_ic_contract_1.py,sha256=bSWsUVjBy5cGtI72cjnkstsMzuUJbB3IG5JjTxOF-dc,500
20
+ tests/artifact/contracts/duplicate_ic_contract_2.py,sha256=bSWsUVjBy5cGtI72cjnkstsMzuUJbB3IG5JjTxOF-dc,500
19
21
  tests/artifact/contracts/not_ic_contract.py,sha256=hQyGnYiiVceYdLI2WrvcFgPqzy1S4-YMb9FPhiHEGSA,510
20
- tests/assertions/test_assertions.py,sha256=y1XE15908Rv3RjWNxU7DtRF2gEGM9eEiHPh9jJHbR4g,1046
22
+ tests/assertions/test_assertions.py,sha256=qzVrOdOM4xYtIy1sFHVAD_-naDHOequ23tEN0MELh0k,10781
21
23
  tests/examples/contracts/football_prediction_market.py,sha256=kdouFijjeCdIJyaVJlgXcqbBAXecA9_YdhklSsIW-QM,3219
22
24
  tests/examples/contracts/intelligent_oracle.py,sha256=WrNZWWoi1sz22Azt2EXgdWHDg5Ihca2pWUHrM9pVfQE,12319
23
25
  tests/examples/contracts/intelligent_oracle_factory.py,sha256=ax496IZuDCA728rRcbjwTaM4Q4E-Y1jGkHsEcyf1cig,1490
@@ -46,8 +48,8 @@ tests/examples/tests/test_user_storage.py,sha256=QEgt2p22LAyzBnBb0YW4BWa_Jasrt15
46
48
  tests/examples/tests/test_wizard_of_coin.py,sha256=aUDeV5w0XONMMS71Vzw80lHfcSM0z8RKPJSXAuDwRto,392
47
49
  tests/plugin/conftest.py,sha256=RKdoE5_zcMimeojAoA_GSFI9du4pMzMi1vZ1njtfoAs,28
48
50
  tests/plugin/test_plugin_hooks.py,sha256=FQOrkhoXLinq0sjvoYjr63Oqg-ZVPcNFeUrK4bqrn4E,2020
49
- genlayer_test-0.1.2.dist-info/METADATA,sha256=05aK4xzg2NrGQIAcFkWS6NNzvFyxVkF_jCqSZhgk8po,13471
50
- genlayer_test-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- genlayer_test-0.1.2.dist-info/entry_points.txt,sha256=rXhrPVq2IhVsd4uWzxzwCTx7jA1KcQIVNxDCUuxq4f8,89
52
- genlayer_test-0.1.2.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
53
- genlayer_test-0.1.2.dist-info/RECORD,,
51
+ genlayer_test-0.2.0.dist-info/METADATA,sha256=_okLWSmaUrqZVrIZ5fLCJvXFBlhmUoItqEoioj2JV2U,14814
52
+ genlayer_test-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ genlayer_test-0.2.0.dist-info/entry_points.txt,sha256=rXhrPVq2IhVsd4uWzxzwCTx7jA1KcQIVNxDCUuxq4f8,89
54
+ genlayer_test-0.2.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
55
+ genlayer_test-0.2.0.dist-info/RECORD,,
@@ -1,3 +1,6 @@
1
- from .contract import find_contract_definition
1
+ from .contract import (
2
+ find_contract_definition_from_name,
3
+ find_contract_definition_from_path,
4
+ )
2
5
 
3
- __all__ = ["find_contract_definition"]
6
+ __all__ = ["find_contract_definition_from_name", "find_contract_definition_from_path"]
@@ -10,7 +10,7 @@ from typing import Union
10
10
 
11
11
  @dataclass
12
12
  class ContractDefinition:
13
- """Class that represents a contract definition from a .gpy file."""
13
+ """Class that represents a contract definition from a contract file."""
14
14
 
15
15
  contract_name: str
16
16
  contract_code: Union[str, bytes]
@@ -20,6 +20,8 @@ class ContractDefinition:
20
20
 
21
21
  def search_path_by_class_name(contracts_dir: Path, contract_name: str) -> Path:
22
22
  """Search for a file by class name in the contracts directory."""
23
+ matching_files = []
24
+
23
25
  for file_path in contracts_dir.rglob("*"):
24
26
  if not file_path.suffix in [".gpy", ".py"]:
25
27
  continue
@@ -40,10 +42,24 @@ def search_path_by_class_name(contracts_dir: Path, contract_name: str) -> Path:
40
42
  and base.value.id == "gl"
41
43
  and base.attr == "Contract"
42
44
  ):
43
- return file_path
45
+ matching_files.append(file_path)
46
+ break
47
+ break
44
48
  except Exception as e:
45
- raise ValueError(f"Error reading file {file_path}: {e}")
46
- raise FileNotFoundError(f"Contract {contract_name} not found at: {contracts_dir}")
49
+ raise ValueError(f"Error reading file {file_path}: {e}") from e
50
+
51
+ if len(matching_files) == 0:
52
+ raise FileNotFoundError(
53
+ f"Contract {contract_name} not found at: {contracts_dir}"
54
+ )
55
+ if len(matching_files) > 1:
56
+ file_paths_str = ", ".join(str(f) for f in matching_files)
57
+ raise ValueError(
58
+ f"Multiple contracts named '{contract_name}' found in contracts directory. "
59
+ f"Found in files: {file_paths_str}. Please ensure contract names are unique."
60
+ ) from None
61
+
62
+ return matching_files[0]
47
63
 
48
64
 
49
65
  def compute_contract_code(
@@ -71,14 +87,35 @@ def compute_contract_code(
71
87
  return buffer.getvalue()
72
88
 
73
89
 
74
- def find_contract_definition(contract_name: str) -> Optional[ContractDefinition]:
75
- """
76
- Search in the contracts directory for a contract definition.
77
- """
78
- contracts_dir = get_contracts_dir()
79
- if not contracts_dir.exists():
80
- raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
81
- main_file_path = search_path_by_class_name(contracts_dir, contract_name)
90
+ def _extract_contract_name_from_file(file_path: Path) -> str:
91
+ """Extract contract name from a file by parsing the AST."""
92
+ try:
93
+ with open(file_path, "r") as f:
94
+ content = f.read()
95
+ tree = ast.parse(content)
96
+
97
+ # Search for class definitions that inherit from gl.Contract
98
+ for node in ast.walk(tree):
99
+ if isinstance(node, ast.ClassDef):
100
+ for base in node.bases:
101
+ if (
102
+ isinstance(base, ast.Attribute)
103
+ and isinstance(base.value, ast.Name)
104
+ and base.value.id == "gl"
105
+ and base.attr == "Contract"
106
+ ):
107
+ return node.name
108
+ except Exception as e:
109
+ raise ValueError(f"Error parsing contract file {file_path}: {e}") from e
110
+
111
+ raise ValueError(f"No valid contract class found in {file_path}")
112
+
113
+
114
+ def _create_contract_definition(
115
+ main_file_path: Path, contract_name: str
116
+ ) -> ContractDefinition:
117
+ """Create a ContractDefinition from a main file path and contract name."""
118
+ # Determine if it's a multifile contract
82
119
  main_file_dir = main_file_path.parent
83
120
  runner_file_path = None
84
121
  if main_file_path.name in ["__init__.py", "__init__.gpy"]:
@@ -87,9 +124,47 @@ def find_contract_definition(contract_name: str) -> Optional[ContractDefinition]
87
124
  if not runner_file_path.exists():
88
125
  # No runner file, so it's a single file contract
89
126
  runner_file_path = None
127
+
128
+ # Compute contract code
129
+ contract_code = compute_contract_code(main_file_path, runner_file_path)
130
+
90
131
  return ContractDefinition(
91
132
  contract_name=contract_name,
92
- contract_code=compute_contract_code(main_file_path, runner_file_path),
133
+ contract_code=contract_code,
93
134
  main_file_path=main_file_path,
94
135
  runner_file_path=runner_file_path,
95
136
  )
137
+
138
+
139
+ def find_contract_definition_from_name(
140
+ contract_name: str,
141
+ ) -> Optional[ContractDefinition]:
142
+ """
143
+ Search in the contracts directory for a contract definition.
144
+ """
145
+ contracts_dir = get_contracts_dir()
146
+ if not contracts_dir.exists():
147
+ raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
148
+
149
+ main_file_path = search_path_by_class_name(contracts_dir, contract_name)
150
+ return _create_contract_definition(main_file_path, contract_name)
151
+
152
+
153
+ def find_contract_definition_from_path(
154
+ contract_file_path: Union[str, Path],
155
+ ) -> ContractDefinition:
156
+ """
157
+ Create a ContractDefinition from a given file path relative to the contracts directory.
158
+ """
159
+ contracts_dir = get_contracts_dir()
160
+ if not contracts_dir.exists():
161
+ raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
162
+
163
+ # Resolve the file path relative to contracts directory
164
+ main_file_path = contracts_dir / contract_file_path
165
+ if not main_file_path.exists():
166
+ raise FileNotFoundError(f"Contract file not found at: {main_file_path}")
167
+
168
+ contract_name = _extract_contract_name_from_file(main_file_path)
169
+
170
+ return _create_contract_definition(main_file_path, contract_name)
gltest/assertions.py CHANGED
@@ -1,18 +1,59 @@
1
+ import re
2
+ from typing import Optional
1
3
  from genlayer_py.types import GenLayerTransaction
2
4
 
3
5
 
4
- def tx_execution_succeeded(result: GenLayerTransaction) -> bool:
6
+ def tx_execution_succeeded(
7
+ result: GenLayerTransaction,
8
+ match_std_out: Optional[str] = None,
9
+ match_std_err: Optional[str] = None,
10
+ ) -> bool:
5
11
  if "consensus_data" not in result:
6
12
  return False
7
13
  if "leader_receipt" not in result["consensus_data"]:
8
14
  return False
9
15
  if len(result["consensus_data"]["leader_receipt"]) == 0:
10
16
  return False
11
- if "execution_result" not in result["consensus_data"]["leader_receipt"][0]:
17
+
18
+ leader_receipt = result["consensus_data"]["leader_receipt"][0]
19
+
20
+ if "execution_result" not in leader_receipt:
21
+ return False
22
+
23
+ execution_result = leader_receipt["execution_result"]
24
+
25
+ if execution_result != "SUCCESS":
12
26
  return False
13
- execution_result = result["consensus_data"]["leader_receipt"][0]["execution_result"]
14
- return execution_result == "SUCCESS"
27
+
28
+ if match_std_out is not None or match_std_err is not None:
29
+ if "genvm_result" not in leader_receipt:
30
+ return False
31
+
32
+ genvm_result = leader_receipt["genvm_result"]
33
+
34
+ if match_std_out is not None:
35
+ if "stdout" not in genvm_result:
36
+ return False
37
+ try:
38
+ if not re.search(match_std_out, genvm_result["stdout"]):
39
+ return False
40
+ except re.error:
41
+ return False
42
+
43
+ if match_std_err is not None:
44
+ if "stderr" not in genvm_result:
45
+ return False
46
+ try:
47
+ if not re.search(match_std_err, genvm_result["stderr"]):
48
+ return False
49
+ except re.error:
50
+ return False
51
+ return True
15
52
 
16
53
 
17
- def tx_execution_failed(result: GenLayerTransaction) -> bool:
18
- return not tx_execution_succeeded(result)
54
+ def tx_execution_failed(
55
+ result: GenLayerTransaction,
56
+ match_std_out: Optional[str] = None,
57
+ match_std_err: Optional[str] = None,
58
+ ) -> bool:
59
+ return not tx_execution_succeeded(result, match_std_out, match_std_err)
@@ -4,8 +4,12 @@ from eth_typing import (
4
4
  )
5
5
  from eth_account.signers.local import LocalAccount
6
6
  from typing import Union
7
+ from pathlib import Path
7
8
  from dataclasses import dataclass
8
- from gltest.artifacts import find_contract_definition
9
+ from gltest.artifacts import (
10
+ find_contract_definition_from_name,
11
+ find_contract_definition_from_path,
12
+ )
9
13
  from gltest.assertions import tx_execution_failed
10
14
  from gltest.exceptions import DeploymentError
11
15
  from .client import get_gl_client
@@ -146,13 +150,13 @@ class ContractFactory:
146
150
  contract_code: str
147
151
 
148
152
  @classmethod
149
- def from_artifact(
153
+ def from_name(
150
154
  cls: Type["ContractFactory"], contract_name: str
151
155
  ) -> "ContractFactory":
152
156
  """
153
157
  Create a ContractFactory instance given the contract name.
154
158
  """
155
- contract_info = find_contract_definition(contract_name)
159
+ contract_info = find_contract_definition_from_name(contract_name)
156
160
  if contract_info is None:
157
161
  raise ValueError(
158
162
  f"Contract {contract_name} not found in the contracts directory"
@@ -161,6 +165,19 @@ class ContractFactory:
161
165
  contract_name=contract_name, contract_code=contract_info.contract_code
162
166
  )
163
167
 
168
+ @classmethod
169
+ def from_file_path(
170
+ cls: Type["ContractFactory"], contract_file_path: Union[str, Path]
171
+ ) -> "ContractFactory":
172
+ """
173
+ Create a ContractFactory instance given the contract file path.
174
+ """
175
+ contract_info = find_contract_definition_from_path(contract_file_path)
176
+ return cls(
177
+ contract_name=contract_info.contract_name,
178
+ contract_code=contract_info.contract_code,
179
+ )
180
+
164
181
  def build_contract(
165
182
  self,
166
183
  contract_address: Union[Address, ChecksumAddress],
@@ -237,8 +254,27 @@ class ContractFactory:
237
254
  ) from e
238
255
 
239
256
 
240
- def get_contract_factory(contract_name: str) -> ContractFactory:
257
+ def get_contract_factory(
258
+ contract_name: Optional[str] = None,
259
+ contract_file_path: Optional[Union[str, Path]] = None,
260
+ ) -> ContractFactory:
241
261
  """
242
262
  Get a ContractFactory instance for a contract.
263
+
264
+ Args:
265
+ contract_name: Name of the contract to load from artifacts
266
+ contract_file_path: Path to the contract file to load directly
267
+
268
+ Note: Exactly one of contract_name or contract_file_path must be provided.
243
269
  """
244
- return ContractFactory.from_artifact(contract_name)
270
+ if contract_name is not None and contract_file_path is not None:
271
+ raise ValueError(
272
+ "Only one of contract_name or contract_file_path should be provided"
273
+ )
274
+
275
+ if contract_name is None and contract_file_path is None:
276
+ raise ValueError("Either contract_name or contract_file_path must be provided")
277
+
278
+ if contract_name is not None:
279
+ return ContractFactory.from_name(contract_name)
280
+ return ContractFactory.from_file_path(contract_file_path)
@@ -0,0 +1,22 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ # contract class
7
+ class DuplicateContract(gl.Contract):
8
+ storage: str
9
+
10
+ # constructor
11
+ def __init__(self, initial_storage: str):
12
+ self.storage = initial_storage
13
+
14
+ # read methods must be annotated with view
15
+ @gl.public.view
16
+ def get_storage(self) -> str:
17
+ return self.storage
18
+
19
+ # write method
20
+ @gl.public.write
21
+ def update_storage(self, new_storage: str) -> None:
22
+ self.storage = new_storage
@@ -0,0 +1,22 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ # contract class
7
+ class DuplicateContract(gl.Contract):
8
+ storage: str
9
+
10
+ # constructor
11
+ def __init__(self, initial_storage: str):
12
+ self.storage = initial_storage
13
+
14
+ # read methods must be annotated with view
15
+ @gl.public.view
16
+ def get_storage(self) -> str:
17
+ return self.storage
18
+
19
+ # write method
20
+ @gl.public.write
21
+ def update_storage(self, new_storage: str) -> None:
22
+ self.storage = new_storage
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
  from gltest.artifacts.contract import (
3
- find_contract_definition,
3
+ find_contract_definition_from_name,
4
+ find_contract_definition_from_path,
4
5
  compute_contract_code,
5
6
  )
6
7
  from gltest.plugin_config import set_contracts_dir
@@ -8,8 +9,15 @@ from pathlib import Path
8
9
 
9
10
 
10
11
  def test_single_file():
12
+ """
13
+ Test finding a contract definition by name for a single-file contract.
14
+
15
+ Verifies that the function correctly identifies and loads a contract
16
+ from a single Python file, extracting the contract name and computing
17
+ the contract code without any additional runner files.
18
+ """
11
19
  set_contracts_dir(".")
12
- contract_definition = find_contract_definition("PredictionMarket")
20
+ contract_definition = find_contract_definition_from_name("PredictionMarket")
13
21
 
14
22
  assert contract_definition.contract_name == "PredictionMarket"
15
23
 
@@ -28,8 +36,15 @@ def test_single_file():
28
36
 
29
37
 
30
38
  def test_multiple_files():
39
+ """
40
+ Test finding a contract definition by name for a multi-file contract.
41
+
42
+ Verifies that the function correctly identifies and loads a contract
43
+ from a multi-file structure with __init__.py and runner.json,
44
+ properly packaging all files into a ZIP archive for deployment.
45
+ """
31
46
  set_contracts_dir(".")
32
- contract_definition = find_contract_definition("MultiFileContract")
47
+ contract_definition = find_contract_definition_from_name("MultiFileContract")
33
48
 
34
49
  assert contract_definition.contract_name == "MultiFileContract"
35
50
 
@@ -47,8 +62,15 @@ def test_multiple_files():
47
62
 
48
63
 
49
64
  def test_single_file_legacy():
65
+ """
66
+ Test finding a contract definition by name for a legacy single-file contract.
67
+
68
+ Verifies that the function correctly handles legacy .gpy files,
69
+ maintaining backward compatibility with older contract formats
70
+ while extracting contract name and computing contract code.
71
+ """
50
72
  set_contracts_dir(".")
51
- contract_definition = find_contract_definition("StorageLegacy")
73
+ contract_definition = find_contract_definition_from_name("StorageLegacy")
52
74
 
53
75
  # Assert complete contract definition
54
76
  assert contract_definition.contract_name == "StorageLegacy"
@@ -66,8 +88,15 @@ def test_single_file_legacy():
66
88
 
67
89
 
68
90
  def test_multiple_files_legacy():
91
+ """
92
+ Test finding a contract definition by name for a legacy multi-file contract.
93
+
94
+ Verifies that the function correctly handles legacy multi-file contracts
95
+ with .gpy extension and runner.json, ensuring proper packaging and
96
+ backward compatibility with older contract structures.
97
+ """
69
98
  set_contracts_dir(".")
70
- contract_definition = find_contract_definition("MultiFileContractLegacy")
99
+ contract_definition = find_contract_definition_from_name("MultiFileContractLegacy")
71
100
 
72
101
  # Assert complete contract definition
73
102
  assert contract_definition.contract_name == "MultiFileContractLegacy"
@@ -86,7 +115,233 @@ def test_multiple_files_legacy():
86
115
 
87
116
 
88
117
  def test_class_is_not_intelligent_contract():
118
+ """
119
+ Test error handling when searching for a non-existent contract by name.
120
+
121
+ Verifies that the function raises FileNotFoundError when attempting
122
+ to find a contract that doesn't exist in the contracts directory,
123
+ ensuring proper error handling for invalid contract names.
124
+ """
89
125
  set_contracts_dir(".")
90
126
 
91
127
  with pytest.raises(FileNotFoundError):
92
- _ = find_contract_definition("NotICContract")
128
+ _ = find_contract_definition_from_name("NotICContract")
129
+
130
+
131
+ def test_find_from_path_single_file():
132
+ """
133
+ Test finding a contract definition by file path for a single-file contract.
134
+
135
+ Verifies that the function correctly loads a contract when given a relative
136
+ path to a single Python file, extracting the contract name via AST parsing
137
+ and computing the contract code without additional runner files.
138
+ """
139
+ set_contracts_dir(".")
140
+ contract_definition = find_contract_definition_from_path(
141
+ "examples/contracts/football_prediction_market.py"
142
+ )
143
+
144
+ assert contract_definition.contract_name == "PredictionMarket"
145
+
146
+ # Assert complete contract definition
147
+ expected_main_file_path = Path("examples/contracts/football_prediction_market.py")
148
+ expected_runner_file_path = None
149
+ contract_code = compute_contract_code(
150
+ expected_main_file_path, expected_runner_file_path
151
+ )
152
+ assert contract_definition.contract_code == contract_code
153
+ assert (
154
+ str(contract_definition.main_file_path)
155
+ == "examples/contracts/football_prediction_market.py"
156
+ )
157
+ assert contract_definition.runner_file_path is None
158
+
159
+
160
+ def test_find_from_path_multiple_files():
161
+ """
162
+ Test finding a contract definition by file path for a multi-file contract.
163
+
164
+ Verifies that the function correctly loads a contract when given a relative
165
+ path to __init__.py in a multi-file structure, automatically detecting
166
+ the associated runner.json and packaging all files appropriately.
167
+ """
168
+ set_contracts_dir(".")
169
+ contract_definition = find_contract_definition_from_path(
170
+ "examples/contracts/multi_file_contract/__init__.py"
171
+ )
172
+
173
+ assert contract_definition.contract_name == "MultiFileContract"
174
+
175
+ # Assert complete contract definition
176
+ expected_main_file_path = Path("examples/contracts/multi_file_contract/__init__.py")
177
+ expected_runner_file_path = Path(
178
+ "examples/contracts/multi_file_contract/runner.json"
179
+ )
180
+ assert contract_definition.main_file_path == expected_main_file_path
181
+ assert contract_definition.runner_file_path == expected_runner_file_path
182
+ contract_code = compute_contract_code(
183
+ expected_main_file_path, expected_runner_file_path
184
+ )
185
+ assert contract_definition.contract_code == contract_code
186
+
187
+
188
+ def test_find_from_path_single_file_legacy():
189
+ """
190
+ Test finding a contract definition by file path for a legacy single-file contract.
191
+
192
+ Verifies that the function correctly handles legacy .gpy files when accessed
193
+ by file path, maintaining backward compatibility while extracting contract
194
+ name via AST parsing and computing appropriate contract code.
195
+ """
196
+ set_contracts_dir(".")
197
+ contract_definition = find_contract_definition_from_path(
198
+ "examples/contracts/storage_legacy.gpy"
199
+ )
200
+
201
+ # Assert complete contract definition
202
+ assert contract_definition.contract_name == "StorageLegacy"
203
+ expected_main_file_path = Path("examples/contracts/storage_legacy.gpy")
204
+ expected_runner_file_path = None
205
+ contract_code = compute_contract_code(
206
+ expected_main_file_path, expected_runner_file_path
207
+ )
208
+ assert contract_definition.contract_code == contract_code
209
+ assert (
210
+ str(contract_definition.main_file_path)
211
+ == "examples/contracts/storage_legacy.gpy"
212
+ )
213
+ assert contract_definition.runner_file_path is None
214
+
215
+
216
+ def test_find_from_path_multiple_files_legacy():
217
+ """
218
+ Test finding a contract definition by file path for a legacy multi-file contract.
219
+
220
+ Verifies that the function correctly handles legacy multi-file contracts
221
+ with .gpy extension when accessed by file path, properly detecting
222
+ runner.json and maintaining backward compatibility with older structures.
223
+ """
224
+ set_contracts_dir(".")
225
+ contract_definition = find_contract_definition_from_path(
226
+ "examples/contracts/multi_file_contract_legacy/__init__.gpy"
227
+ )
228
+
229
+ # Assert complete contract definition
230
+ assert contract_definition.contract_name == "MultiFileContractLegacy"
231
+ expected_main_file_path = Path(
232
+ "examples/contracts/multi_file_contract_legacy/__init__.gpy"
233
+ )
234
+ expected_runner_file_path = Path(
235
+ "examples/contracts/multi_file_contract_legacy/runner.json"
236
+ )
237
+ assert contract_definition.main_file_path == expected_main_file_path
238
+ assert contract_definition.runner_file_path == expected_runner_file_path
239
+ contract_code = compute_contract_code(
240
+ expected_main_file_path, expected_runner_file_path
241
+ )
242
+ assert contract_definition.contract_code == contract_code
243
+
244
+
245
+ def test_find_from_path_file_not_found():
246
+ """
247
+ Test error handling when the specified contract file doesn't exist.
248
+
249
+ Verifies that the function raises FileNotFoundError with appropriate
250
+ error message when attempting to load a contract from a non-existent
251
+ file path relative to the contracts directory.
252
+ """
253
+ set_contracts_dir(".")
254
+
255
+ with pytest.raises(FileNotFoundError, match="Contract file not found at:"):
256
+ _ = find_contract_definition_from_path("nonexistent/contract.py")
257
+
258
+
259
+ def test_find_from_path_contracts_dir_not_found():
260
+ """
261
+ Test error handling when the contracts directory doesn't exist.
262
+
263
+ Verifies that the function raises FileNotFoundError with appropriate
264
+ error message when the configured contracts directory is invalid,
265
+ ensuring proper validation before attempting file operations.
266
+ """
267
+ set_contracts_dir("nonexistent_directory")
268
+
269
+ with pytest.raises(FileNotFoundError, match="Contracts directory not found at:"):
270
+ _ = find_contract_definition_from_path("some/contract.py")
271
+
272
+
273
+ def test_find_from_path_no_valid_contract_class():
274
+ """
275
+ Test error handling when a file exists but contains no valid contract class.
276
+
277
+ Verifies that the function raises ValueError with appropriate error message
278
+ when attempting to load a file that exists but doesn't contain a class
279
+ that inherits from gl.Contract, ensuring proper AST parsing validation.
280
+ """
281
+ set_contracts_dir(".")
282
+
283
+ with pytest.raises(ValueError, match="No valid contract class found in"):
284
+ _ = find_contract_definition_from_path("artifact/contracts/not_ic_contract.py")
285
+
286
+
287
+ def test_multiple_contracts_same_name():
288
+ """
289
+ Test error handling when multiple contracts with the same name exist.
290
+
291
+ Verifies that the function raises ValueError with appropriate error message
292
+ when multiple files contain contracts with the same name, listing all
293
+ duplicate file locations and providing guidance for resolution.
294
+ """
295
+ set_contracts_dir(".")
296
+
297
+ with pytest.raises(
298
+ ValueError,
299
+ match=r"Multiple contracts named 'DuplicateContract' found in contracts directory\. Found in files: .+\. Please ensure contract names are unique\.",
300
+ ):
301
+ _ = find_contract_definition_from_name("DuplicateContract")
302
+
303
+
304
+ def test_duplicate_contract_error_message_format():
305
+ """
306
+ Test that the duplicate contract error message contains all expected elements.
307
+
308
+ Verifies that when multiple contracts with the same name are found, the error
309
+ message includes the contract name, mentions "contracts directory", lists
310
+ file paths, and provides clear guidance about ensuring uniqueness.
311
+ """
312
+ set_contracts_dir(".")
313
+
314
+ try:
315
+ _ = find_contract_definition_from_name("DuplicateContract")
316
+ pytest.fail("Expected ValueError for duplicate contracts")
317
+ except ValueError as e:
318
+ error_message = str(e)
319
+ # Verify error message contains key components
320
+ assert "Multiple contracts named 'DuplicateContract' found" in error_message
321
+ assert "contracts directory" in error_message
322
+ assert "Found in files:" in error_message
323
+ assert "Please ensure contract names are unique" in error_message
324
+ # Verify that multiple file paths are mentioned (comma-separated)
325
+ assert (
326
+ "," in error_message
327
+ or len(error_message.split("Found in files: ")[1].split(".")[0]) > 0
328
+ )
329
+ except Exception as e:
330
+ pytest.fail(f"Expected ValueError but got {type(e).__name__}: {e}")
331
+
332
+
333
+ def test_single_contract_still_works_with_duplicate_detection():
334
+ """
335
+ Test that normal single contract loading still works after duplicate detection changes.
336
+
337
+ Verifies that the enhanced search_path_by_class_name function doesn't break
338
+ the normal case where only one contract with a given name exists, ensuring
339
+ backward compatibility with existing functionality.
340
+ """
341
+ set_contracts_dir(".")
342
+
343
+ # This should work normally - no duplicates expected for PredictionMarket
344
+ contract_definition = find_contract_definition_from_name("PredictionMarket")
345
+ assert contract_definition.contract_name == "PredictionMarket"
346
+ assert contract_definition.main_file_path is not None
347
+ assert "football_prediction_market.py" in str(contract_definition.main_file_path)
@@ -10,22 +10,335 @@ GENLAYER_FAILED_TRANSACTION = {
10
10
 
11
11
  GENLAYER_EMPTY_LEADER_RECEIPT = {"consensus_data": {"leader_receipt": []}}
12
12
 
13
+ GENLAYER_GENVM_TRANSACTION = {
14
+ "consensus_data": {
15
+ "leader_receipt": [
16
+ {
17
+ "execution_result": "SUCCESS",
18
+ "genvm_result": {
19
+ "stdout": "Process completed successfully with code 123 items",
20
+ "stderr": "Warning: deprecated function used",
21
+ },
22
+ }
23
+ ]
24
+ }
25
+ }
26
+
27
+ GENLAYER_GENVM_EMPTY_STDERR = {
28
+ "consensus_data": {
29
+ "leader_receipt": [
30
+ {
31
+ "execution_result": "SUCCESS",
32
+ "genvm_result": {
33
+ "stdout": "Task finished without errors",
34
+ "stderr": "",
35
+ },
36
+ }
37
+ ]
38
+ }
39
+ }
40
+
41
+ GENLAYER_GENVM_NO_STDOUT = {
42
+ "consensus_data": {
43
+ "leader_receipt": [
44
+ {
45
+ "execution_result": "SUCCESS",
46
+ "genvm_result": {"stderr": "Error occurred"},
47
+ }
48
+ ]
49
+ }
50
+ }
51
+
52
+ GENLAYER_GENVM_FAILED = {
53
+ "consensus_data": {
54
+ "leader_receipt": [
55
+ {
56
+ "execution_result": "ERROR",
57
+ "genvm_result": {
58
+ "stdout": "Process failed",
59
+ "stderr": "Critical error occurred",
60
+ },
61
+ }
62
+ ]
63
+ }
64
+ }
65
+
13
66
 
14
67
  def test_with_successful_transaction():
68
+ """Test assertion functions with a basic successful transaction.
69
+
70
+ Validates that:
71
+ - tx_execution_succeeded returns True for successful transactions
72
+ - tx_execution_failed returns False for successful transactions
73
+ """
15
74
  assert tx_execution_succeeded(GENLAYER_SUCCESS_TRANSACTION) is True
16
75
  assert tx_execution_failed(GENLAYER_SUCCESS_TRANSACTION) is False
17
76
 
18
77
 
19
78
  def test_with_failed_transaction():
79
+ """Test assertion functions with a basic failed transaction.
80
+
81
+ Validates that:
82
+ - tx_execution_succeeded returns False for failed transactions
83
+ - tx_execution_failed returns True for failed transactions
84
+ """
20
85
  assert tx_execution_succeeded(GENLAYER_FAILED_TRANSACTION) is False
21
86
  assert tx_execution_failed(GENLAYER_FAILED_TRANSACTION) is True
22
87
 
23
88
 
24
89
  def test_with_empty_leader_receipt():
90
+ """Test assertion functions with empty leader_receipt array.
91
+
92
+ Validates that:
93
+ - Both functions handle empty leader_receipt gracefully
94
+ - Empty leader_receipt is treated as a failed transaction
95
+ """
25
96
  assert tx_execution_succeeded(GENLAYER_EMPTY_LEADER_RECEIPT) is False
26
97
  assert tx_execution_failed(GENLAYER_EMPTY_LEADER_RECEIPT) is True
27
98
 
28
99
 
29
100
  def test_with_invalid_transaction():
101
+ """Test assertion functions with completely invalid transaction structure.
102
+
103
+ Validates that:
104
+ - Both functions handle malformed transactions gracefully
105
+ - Invalid transactions are treated as failed
106
+ """
30
107
  assert tx_execution_succeeded({}) is False
31
108
  assert tx_execution_failed({}) is True
109
+
110
+
111
+ def test_genvm_result_without_match():
112
+ """Test assertion functions with genvm_result but no match parameters.
113
+
114
+ Validates that:
115
+ - Transactions with genvm_result succeed when execution_result is SUCCESS
116
+ - No match parameters means only basic execution status is checked
117
+ """
118
+ assert tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION) is True
119
+ assert tx_execution_failed(GENLAYER_GENVM_TRANSACTION) is False
120
+
121
+
122
+ def test_match_std_out_simple_string():
123
+ """Test stdout matching with simple string patterns.
124
+
125
+ Validates that:
126
+ - Simple string matching works correctly in stdout
127
+ - Non-matching strings cause the assertion to fail
128
+ - tx_execution_failed behaves oppositely to tx_execution_succeeded
129
+ """
130
+ assert (
131
+ tx_execution_succeeded(
132
+ GENLAYER_GENVM_TRANSACTION, match_std_out="Process completed"
133
+ )
134
+ is True
135
+ )
136
+ assert (
137
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_out="nonexistent")
138
+ is False
139
+ )
140
+ assert (
141
+ tx_execution_failed(GENLAYER_GENVM_TRANSACTION, match_std_out="nonexistent")
142
+ is True
143
+ )
144
+
145
+
146
+ def test_match_std_err_simple_string():
147
+ """Test stderr matching with simple string patterns.
148
+
149
+ Validates that:
150
+ - Simple string matching works correctly in stderr
151
+ - Non-matching strings cause the assertion to fail
152
+ - tx_execution_failed behaves oppositely to tx_execution_succeeded
153
+ """
154
+ assert (
155
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_err="Warning")
156
+ is True
157
+ )
158
+ assert (
159
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_err="nonexistent")
160
+ is False
161
+ )
162
+ assert (
163
+ tx_execution_failed(GENLAYER_GENVM_TRANSACTION, match_std_err="nonexistent")
164
+ is True
165
+ )
166
+
167
+
168
+ def test_match_std_out_regex():
169
+ """Test stdout matching with regex patterns.
170
+
171
+ Validates that:
172
+ - Complex regex patterns work correctly in stdout
173
+ - Different regex patterns match appropriately
174
+ - Non-matching regex patterns cause assertions to fail
175
+ - Tests various regex features like \\d+, .*, word boundaries
176
+ """
177
+ assert (
178
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_out=r".*code \d+")
179
+ is True
180
+ )
181
+ assert (
182
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_out=r".* 123 .*")
183
+ is True
184
+ )
185
+ assert (
186
+ tx_execution_succeeded(
187
+ GENLAYER_GENVM_TRANSACTION, match_std_out=r"Process.*successfully"
188
+ )
189
+ is True
190
+ )
191
+ assert (
192
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_out=r"code \d{4}")
193
+ is False
194
+ )
195
+
196
+
197
+ def test_match_std_err_regex():
198
+ """Test stderr matching with regex patterns.
199
+
200
+ Validates that:
201
+ - Complex regex patterns work correctly in stderr
202
+ - Different regex patterns match appropriately
203
+ - Non-matching regex patterns cause assertions to fail
204
+ - Tests various regex features like .*, word boundaries
205
+ """
206
+ assert (
207
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_err=r"Warning:.*")
208
+ is True
209
+ )
210
+ assert (
211
+ tx_execution_succeeded(
212
+ GENLAYER_GENVM_TRANSACTION, match_std_err=r".*deprecated.*"
213
+ )
214
+ is True
215
+ )
216
+ assert (
217
+ tx_execution_succeeded(GENLAYER_GENVM_TRANSACTION, match_std_err=r"Error:.*")
218
+ is False
219
+ )
220
+
221
+
222
+ def test_match_both_stdout_and_stderr():
223
+ """Test matching both stdout and stderr simultaneously.
224
+
225
+ Validates that:
226
+ - Both stdout and stderr patterns must match for success
227
+ - If either pattern fails to match, the assertion fails
228
+ - Combined matching works with both simple strings and regex
229
+ """
230
+ assert (
231
+ tx_execution_succeeded(
232
+ GENLAYER_GENVM_TRANSACTION,
233
+ match_std_out="Process completed",
234
+ match_std_err="Warning",
235
+ )
236
+ is True
237
+ )
238
+
239
+ assert (
240
+ tx_execution_succeeded(
241
+ GENLAYER_GENVM_TRANSACTION,
242
+ match_std_out="Process completed",
243
+ match_std_err="nonexistent",
244
+ )
245
+ is False
246
+ )
247
+
248
+
249
+ def test_match_empty_stderr():
250
+ """Test matching empty stderr with different approaches.
251
+
252
+ Validates that:
253
+ - Empty stderr can be matched with regex pattern ^$
254
+ - Empty stderr can be matched with empty string
255
+ - Non-empty patterns fail when stderr is empty
256
+ """
257
+ assert (
258
+ tx_execution_succeeded(GENLAYER_GENVM_EMPTY_STDERR, match_std_err=r"^$") is True
259
+ )
260
+ assert tx_execution_succeeded(GENLAYER_GENVM_EMPTY_STDERR, match_std_err="") is True
261
+ assert (
262
+ tx_execution_succeeded(GENLAYER_GENVM_EMPTY_STDERR, match_std_err="Warning")
263
+ is False
264
+ )
265
+
266
+
267
+ def test_missing_stdout_field():
268
+ """Test behavior when stdout field is missing from genvm_result.
269
+
270
+ Validates that:
271
+ - Missing stdout field causes match_std_out to fail
272
+ - The assertion handles missing fields gracefully
273
+ - tx_execution_failed returns True when stdout matching is requested but field is missing
274
+ """
275
+ assert (
276
+ tx_execution_succeeded(GENLAYER_GENVM_NO_STDOUT, match_std_out="anything")
277
+ is False
278
+ )
279
+ assert (
280
+ tx_execution_failed(GENLAYER_GENVM_NO_STDOUT, match_std_out="anything") is True
281
+ )
282
+
283
+
284
+ def test_missing_stderr_field():
285
+ """Test behavior when stderr field is missing from genvm_result.
286
+
287
+ Validates that:
288
+ - Missing stderr field causes match_std_err to fail
289
+ - The assertion handles missing fields gracefully
290
+ - tx_execution_failed returns True when stderr matching is requested but field is missing
291
+ """
292
+ genvm_no_stderr = {
293
+ "consensus_data": {
294
+ "leader_receipt": [
295
+ {"execution_result": "SUCCESS", "genvm_result": {"stdout": "Success"}}
296
+ ]
297
+ }
298
+ }
299
+ assert tx_execution_succeeded(genvm_no_stderr, match_std_err="anything") is False
300
+ assert tx_execution_failed(genvm_no_stderr, match_std_err="anything") is True
301
+
302
+
303
+ def test_failed_execution_with_genvm_result():
304
+ """Test assertion behavior with failed execution but present genvm_result.
305
+
306
+ Validates that:
307
+ - execution_result takes precedence over output matching
308
+ - Even with matching stdout/stderr, failed executions return False
309
+ - The basic execution status is checked before output matching
310
+ """
311
+ assert tx_execution_succeeded(GENLAYER_GENVM_FAILED) is False
312
+ assert tx_execution_failed(GENLAYER_GENVM_FAILED) is True
313
+
314
+ # Even with matching stdout/stderr, should still fail if execution_result is not SUCCESS
315
+ assert (
316
+ tx_execution_succeeded(GENLAYER_GENVM_FAILED, match_std_out="Process failed")
317
+ is False
318
+ )
319
+ assert (
320
+ tx_execution_succeeded(GENLAYER_GENVM_FAILED, match_std_err="Critical error")
321
+ is False
322
+ )
323
+
324
+
325
+ def test_missing_genvm_result_with_match():
326
+ """Test behavior when genvm_result is missing but match parameters are provided.
327
+
328
+ Validates that:
329
+ - Missing genvm_result causes output matching to fail
330
+ - Both stdout and stderr matching fail when genvm_result is absent
331
+ - tx_execution_failed returns True when match parameters are used without genvm_result
332
+ """
333
+ assert (
334
+ tx_execution_succeeded(GENLAYER_SUCCESS_TRANSACTION, match_std_out="anything")
335
+ is False
336
+ )
337
+ assert (
338
+ tx_execution_succeeded(GENLAYER_SUCCESS_TRANSACTION, match_std_err="anything")
339
+ is False
340
+ )
341
+ assert (
342
+ tx_execution_failed(GENLAYER_SUCCESS_TRANSACTION, match_std_out="anything")
343
+ is True
344
+ )