1- """Pytest plugin for configuring and installing the solc compiler."""
1+ """Pytest plugin for configuring and verifying the solc compiler."""
22
3- import platform
43import subprocess
5- from argparse import ArgumentTypeError
64from shutil import which
75
86import pytest
9- import solc_select .solc_select as solc_select # type: ignore
107from pytest_metadata .plugin import metadata_key # type: ignore
118from semver import Version
129
1310from ethereum_test_forks import Frontier
14- from ethereum_test_tools .code import Solc
15-
16- DEFAULT_SOLC_VERSION = "0.8.24"
1711
1812
1913def pytest_addoption (parser : pytest .Parser ):
@@ -26,110 +20,109 @@ def pytest_addoption(parser: pytest.Parser):
2620 type = str ,
2721 default = None ,
2822 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."
3124 ),
3225 )
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- )
4026
4127
4228@pytest .hookimpl (tryfirst = True )
4329def 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."""
4931 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- )
5632
33+ # Use provided solc binary or find it in PATH
5734 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+ )
6040 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+ )
9186
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
9299 if "Tools" not in config .stash [metadata_key ]:
93100 config .stash [metadata_key ]["Tools" ] = {
94101 "solc" : str (solc_version_semver ),
95102 }
96103 else :
97104 config .stash [metadata_key ]["Tools" ]["solc" ] = str (solc_version_semver )
98105
106+ # Check minimum version requirement
99107 if solc_version_semver < Frontier .solc_min_version ():
100108 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 "
102110 f"{ Frontier .solc_min_version ()} " ,
103111 returncode = pytest .ExitCode .USAGE_ERROR ,
104112 )
113+
114+ # Store for later use
105115 config .solc_version = solc_version_semver # type: ignore
116+ config .option .solc_bin = solc_bin # save for fixture
106117
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 } " )
127120
128121
129122@pytest .fixture (autouse = True , scope = "session" )
130123def solc_bin (request : pytest .FixtureRequest ):
131124 """Return configured solc binary path."""
132- return request .config .getoption ("solc_bin" )
125+ return request .config .getoption ("solc_bin" ) or which ( "solc" )
133126
134127
135128@pytest .hookimpl (trylast = True )
@@ -138,4 +131,5 @@ def pytest_report_header(config, start_path):
138131 if config .option .collectonly :
139132 return
140133 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