1
- """Pytest plugin for configuring and installing the solc compiler."""
1
+ """Pytest plugin for configuring and verifying the solc compiler."""
2
2
3
- import platform
4
3
import subprocess
5
- from argparse import ArgumentTypeError
6
4
from shutil import which
7
5
8
6
import pytest
9
- import solc_select .solc_select as solc_select # type: ignore
10
7
from pytest_metadata .plugin import metadata_key # type: ignore
11
8
from semver import Version
12
9
13
10
from ethereum_test_forks import Frontier
14
- from ethereum_test_tools .code import Solc
15
-
16
- DEFAULT_SOLC_VERSION = "0.8.24"
17
11
18
12
19
13
def pytest_addoption (parser : pytest .Parser ):
@@ -26,110 +20,109 @@ def pytest_addoption(parser: pytest.Parser):
26
20
type = str ,
27
21
default = None ,
28
22
help = (
29
- "Path to a solc executable (for Yul source compilation). "
30
- "No default; if unspecified `--solc-version` is used."
23
+ "Path to a solc executable (for Yul source compilation). Default: solc binary in PATH."
31
24
),
32
25
)
33
- solc_group .addoption (
34
- "--solc-version" ,
35
- action = "store" ,
36
- dest = "solc_version" ,
37
- default = None ,
38
- help = f"Version of the solc compiler to use. Default: { DEFAULT_SOLC_VERSION } ." ,
39
- )
40
26
41
27
42
28
@pytest .hookimpl (tryfirst = True )
43
29
def pytest_configure (config : pytest .Config ):
44
- """
45
- Ensure that the specified solc version is:
46
- - available if --solc_bin has been specified,
47
- - installed via solc_select if --solc_version has been specified.
48
- """
30
+ """Ensure that solc is available and get its version."""
49
31
solc_bin = config .getoption ("solc_bin" )
50
- solc_version = config .getoption ("solc_version" )
51
-
52
- if solc_bin and solc_version :
53
- raise pytest .UsageError (
54
- "You cannot specify both --solc-bin and --solc-version. Please choose one."
55
- )
56
32
33
+ # Use provided solc binary or find it in PATH
57
34
if solc_bin :
58
- # will raise an error if the solc binary is not found.
59
- solc_version_semver = Solc (config .getoption ("solc_bin" )).version
35
+ if not which (solc_bin ):
36
+ pytest .exit (
37
+ f"Specified solc binary not found: { solc_bin } " ,
38
+ returncode = pytest .ExitCode .USAGE_ERROR ,
39
+ )
60
40
else :
61
- # if no solc binary is specified, use solc-select
62
- solc_version = solc_version or DEFAULT_SOLC_VERSION
63
- try :
64
- version , _ = solc_select .current_version ()
65
- except ArgumentTypeError :
66
- version = None
67
- if version != solc_version :
68
- # solc-select current does not support ARM linux
69
- if platform .system ().lower () == "linux" and platform .machine ().lower () == "aarch64" :
70
- error_message = (
71
- f"Version { version } does not match solc_version { solc_version } "
72
- "and since solc-select currently does not support ARM linux you must "
73
- "manually do the following: "
74
- "Build solc from source, and manually move the binary to "
75
- ".venv/.solc-select/artifacts/solc-x.y.z/solc-x.y.z, then run "
76
- "'uv run solc-select use <x.y.z>'"
77
- )
78
- pytest .exit (error_message , returncode = pytest .ExitCode .USAGE_ERROR )
79
-
80
- if config .getoption ("verbose" ) > 0 :
81
- print (f"Setting solc version { solc_version } via solc-select..." )
82
- try :
83
- solc_select .switch_global_version (solc_version , always_install = True )
84
- except Exception as e :
85
- message = f"Failed to install solc version { solc_version } : { e } . "
86
- if isinstance (e , ArgumentTypeError ):
87
- message += "\n List available versions using `uv run solc-select install`."
88
- pytest .exit (message , returncode = pytest .ExitCode .USAGE_ERROR )
89
- solc_version_semver = Version .parse (solc_version )
90
- config .option .solc_bin = which ("solc" ) # save for fixture
41
+ solc_bin = which ("solc" )
42
+ if not solc_bin :
43
+ pytest .exit (
44
+ "solc binary not found in PATH. Please install solc and ensure it's in your PATH." ,
45
+ returncode = pytest .ExitCode .USAGE_ERROR ,
46
+ )
47
+
48
+ # Get solc version using subprocess
49
+ try :
50
+ result = subprocess .run (
51
+ [solc_bin , "--version" ],
52
+ stdout = subprocess .PIPE ,
53
+ stderr = subprocess .STDOUT ,
54
+ text = True ,
55
+ check = True ,
56
+ timeout = 10 ,
57
+ )
58
+ except subprocess .CalledProcessError as e :
59
+ pytest .exit (
60
+ f"Failed to get solc version. Command output: { e .stdout } " ,
61
+ returncode = pytest .ExitCode .USAGE_ERROR ,
62
+ )
63
+ except subprocess .TimeoutExpired :
64
+ pytest .exit ("Timeout while getting solc version." , returncode = pytest .ExitCode .USAGE_ERROR )
65
+ except Exception as e :
66
+ pytest .exit (
67
+ f"Unexpected error while getting solc version: { e } " ,
68
+ returncode = pytest .ExitCode .USAGE_ERROR ,
69
+ )
70
+
71
+ # Parse version from output
72
+ version_output = result .stdout
73
+ version_line = None
74
+
75
+ # Look for version in output (format: "Version: X.Y.Z+commit.hash")
76
+ for line in version_output .split ("\n " ):
77
+ if line .startswith ("Version:" ):
78
+ version_line = line
79
+ break
80
+
81
+ if not version_line :
82
+ pytest .exit (
83
+ f"Could not parse solc version from output:\n { version_output } " ,
84
+ returncode = pytest .ExitCode .USAGE_ERROR ,
85
+ )
91
86
87
+ # Extract version number
88
+ try :
89
+ # Format is typically "Version: 0.8.24+commit.e11b9ed9.Linux.g++"
90
+ version_str = version_line .split ()[1 ].split ("+" )[0 ]
91
+ solc_version_semver = Version .parse (version_str )
92
+ except (IndexError , ValueError ) as e :
93
+ pytest .exit (
94
+ f"Failed to parse solc version from: { version_line } \n Error: { e } " ,
95
+ returncode = pytest .ExitCode .USAGE_ERROR ,
96
+ )
97
+
98
+ # Store version in metadata
92
99
if "Tools" not in config .stash [metadata_key ]:
93
100
config .stash [metadata_key ]["Tools" ] = {
94
101
"solc" : str (solc_version_semver ),
95
102
}
96
103
else :
97
104
config .stash [metadata_key ]["Tools" ]["solc" ] = str (solc_version_semver )
98
105
106
+ # Check minimum version requirement
99
107
if solc_version_semver < Frontier .solc_min_version ():
100
108
pytest .exit (
101
- f"Unsupported solc version: { solc_version } . Minimum required version is "
109
+ f"Unsupported solc version: { solc_version_semver } . Minimum required version is "
102
110
f"{ Frontier .solc_min_version ()} " ,
103
111
returncode = pytest .ExitCode .USAGE_ERROR ,
104
112
)
113
+
114
+ # Store for later use
105
115
config .solc_version = solc_version_semver # type: ignore
116
+ config .option .solc_bin = solc_bin # save for fixture
106
117
107
- # test whether solc_version matches actual one
108
- # using subprocess because that's how yul is compiled in
109
- # ./src/ethereum_test_specs/static_state/common/compile_yul.py
110
- expected_solc_version_string : str = str (solc_version_semver )
111
- actual_solc_version = subprocess .run (
112
- ["solc" , "--version" ],
113
- stdout = subprocess .PIPE ,
114
- stderr = subprocess .STDOUT ,
115
- text = True ,
116
- check = True ,
117
- )
118
- actual_solc_version_string = actual_solc_version .stdout
119
- # use only look at first 10 chars to pass e.g.
120
- # actual: 0.8.25+commit.b61c2a91.Linux.g++ should pass with expected: "0.8.25+commit.b61c2a91
121
- if (
122
- expected_solc_version_string [:10 ] not in actual_solc_version_string
123
- ) or expected_solc_version_string == "" :
124
- error_message = f"Expected solc version { solc_version_semver } but detected a\
125
- different solc version:\n { actual_solc_version_string } \n Critical error, aborting.."
126
- pytest .exit (error_message , returncode = pytest .ExitCode .USAGE_ERROR )
118
+ if config .getoption ("verbose" ) > 0 :
119
+ print (f"Using solc version { solc_version_semver } from { solc_bin } " )
127
120
128
121
129
122
@pytest .fixture (autouse = True , scope = "session" )
130
123
def solc_bin (request : pytest .FixtureRequest ):
131
124
"""Return configured solc binary path."""
132
- return request .config .getoption ("solc_bin" )
125
+ return request .config .getoption ("solc_bin" ) or which ( "solc" )
133
126
134
127
135
128
@pytest .hookimpl (trylast = True )
@@ -138,4 +131,5 @@ def pytest_report_header(config, start_path):
138
131
if config .option .collectonly :
139
132
return
140
133
solc_version = config .stash [metadata_key ]["Tools" ]["solc" ]
141
- return [(f"solc: { solc_version } " )]
134
+ solc_path = config .option .solc_bin or which ("solc" )
135
+ return [f"solc: { solc_version } " , f"solc path: { solc_path } " ]
0 commit comments