Skip to content

Commit 1a3122a

Browse files
committed
Let geth choose the open port, pass it to fixtures
1 parent c9e756d commit 1a3122a

11 files changed

+158
-217
lines changed

tests/core/providers/test_legacy_websocket_provider.py

-52
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
import pytest
2-
import asyncio
3-
from asyncio.exceptions import (
4-
TimeoutError,
5-
)
6-
from threading import (
7-
Thread,
8-
)
9-
10-
from websockets.legacy.server import (
11-
serve,
12-
)
132

14-
from tests.utils import (
15-
wait_for_ws,
16-
)
173
from web3 import (
184
Web3,
195
)
@@ -26,39 +12,6 @@
2612
)
2713

2814

29-
@pytest.fixture
30-
def start_websocket_server(open_port):
31-
event_loop = asyncio.new_event_loop()
32-
33-
def run_server():
34-
async def empty_server(websocket, path):
35-
data = await websocket.recv()
36-
await asyncio.sleep(0.02)
37-
await websocket.send(data)
38-
39-
asyncio.set_event_loop(event_loop)
40-
server = serve(empty_server, "127.0.0.1", open_port)
41-
event_loop.run_until_complete(server)
42-
event_loop.run_forever()
43-
44-
thd = Thread(target=run_server)
45-
thd.start()
46-
try:
47-
yield
48-
finally:
49-
event_loop.call_soon_threadsafe(event_loop.stop)
50-
51-
52-
@pytest.fixture
53-
def w3(open_port, start_websocket_server):
54-
# need new event loop as the one used by server is already running
55-
event_loop = asyncio.new_event_loop()
56-
endpoint_uri = f"ws://127.0.0.1:{open_port}"
57-
event_loop.run_until_complete(wait_for_ws(endpoint_uri))
58-
provider = LegacyWebSocketProvider(endpoint_uri, websocket_timeout=0.01)
59-
return Web3(provider)
60-
61-
6215
def test_no_args():
6316
provider = LegacyWebSocketProvider()
6417
w3 = Web3(provider)
@@ -69,11 +22,6 @@ def test_no_args():
6922
w3.is_connected(show_traceback=True)
7023

7124

72-
def test_websocket_provider_timeout(w3):
73-
with pytest.raises(TimeoutError):
74-
w3.eth.accounts
75-
76-
7725
def test_restricted_websocket_kwargs():
7826
invalid_kwargs = {"uri": "ws://127.0.0.1:8546"}
7927
re_exc_message = f".*found: {set(invalid_kwargs)!r}*"

tests/integration/go_ethereum/conftest.py

+85-22
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
from pathlib import (
55
Path,
66
)
7+
import re
78
import subprocess
9+
import time
810
import zipfile
911

1012
from eth_utils import (
1113
is_dict,
12-
to_text,
1314
)
1415
import pytest_asyncio
1516

@@ -28,6 +29,9 @@
2829
from web3._utils.contract_sources.contract_data.storage_contract import (
2930
STORAGE_CONTRACT_DATA,
3031
)
32+
from web3.exceptions import (
33+
Web3Exception,
34+
)
3135

3236
from .utils import (
3337
kill_proc_gracefully,
@@ -36,6 +40,13 @@
3640
KEYFILE_PW = "web3py-test"
3741
GETH_FIXTURE_ZIP = "geth-1.15.5-fixture.zip"
3842

43+
PORT_REGEX = re.compile(r"127\.0\.0\.1:(\d+)")
44+
INTERRUPT_REGEX = re.compile(r"interrupt, shutting down")
45+
46+
47+
class ShouldRestartGethProcess(Web3Exception):
48+
pass
49+
3950

4051
@pytest.fixture
4152
def geth_binary():
@@ -133,36 +144,88 @@ def genesis_file(datadir):
133144
return genesis_file_path
134145

135146

147+
def wait_for_port(proc, timeout=10):
148+
start = time.time()
149+
port = None
150+
wait_time = start + timeout
151+
152+
while time.time() < wait_time:
153+
line = proc.stderr.readline()
154+
if not line:
155+
continue
156+
157+
if port is not None:
158+
if INTERRUPT_REGEX.search(line):
159+
raise ShouldRestartGethProcess(
160+
"Geth process interrupted after port assignment"
161+
)
162+
else:
163+
if match := PORT_REGEX.search(line):
164+
port = int(match.group(1))
165+
if port == 80:
166+
port = None
167+
else:
168+
# found port, but give a grace period for any interrupt called
169+
wait_time = time.time() + 0.1
170+
171+
if port is None:
172+
raise TimeoutError(f"Did not find port in logs within {timeout} seconds")
173+
return port
174+
175+
136176
@pytest.fixture
137-
def geth_process(geth_binary, datadir, genesis_file, geth_command_arguments):
138-
init_datadir_command = (
177+
def start_geth_process_and_yield_port(
178+
geth_binary, datadir, genesis_file, geth_command_arguments
179+
):
180+
init_cmd = (
139181
geth_binary,
140182
"--datadir",
141183
str(datadir),
142184
"init",
143185
str(genesis_file),
144186
)
145-
subprocess.check_output(
146-
init_datadir_command,
147-
stdin=subprocess.PIPE,
148-
stderr=subprocess.PIPE,
149-
)
150-
proc = subprocess.Popen(
151-
geth_command_arguments,
152-
stdin=subprocess.PIPE,
153-
stdout=subprocess.PIPE,
154-
stderr=subprocess.PIPE,
155-
)
187+
subprocess.check_output(init_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
188+
189+
tries = 2
190+
proc = None
191+
port = None
192+
193+
while tries > 0:
194+
proc = subprocess.Popen(
195+
geth_command_arguments,
196+
stdout=subprocess.PIPE,
197+
stderr=subprocess.PIPE,
198+
text=True,
199+
bufsize=1,
200+
)
201+
try:
202+
port = wait_for_port(proc)
203+
break # success
204+
except ShouldRestartGethProcess:
205+
kill_proc_gracefully(proc)
206+
tries -= 1
207+
if tries == 0:
208+
raise RuntimeError("Failed to start Geth after retries")
209+
except Exception:
210+
kill_proc_gracefully(proc)
211+
raise # bubble up real errors
212+
213+
if port is None:
214+
raise RuntimeError("Failed to obtain port after Geth start")
215+
156216
try:
157-
yield proc
217+
yield port
158218
finally:
159-
kill_proc_gracefully(proc)
160-
output, errors = proc.communicate()
161-
print(
162-
"Geth Process Exited:\n"
163-
f"stdout:{to_text(output)}\n\n"
164-
f"stderr:{to_text(errors)}\n\n"
165-
)
219+
if proc:
220+
kill_proc_gracefully(proc)
221+
try:
222+
output, errors = proc.communicate(timeout=5)
223+
except subprocess.TimeoutExpired:
224+
proc.kill()
225+
output, errors = proc.communicate()
226+
print(
227+
"Geth Process Exited:\n" f"stdout:{output}\n\n" f"stderr:{errors}\n\n"
228+
)
166229

167230

168231
@pytest.fixture

tests/integration/go_ethereum/test_goethereum_http.py

+21-39
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
)
66
import pytest_asyncio
77

8-
from tests.utils import (
9-
get_open_port,
10-
)
118
from web3 import (
129
AsyncWeb3,
1310
Web3,
@@ -32,29 +29,15 @@
3229
GoEthereumTxPoolModuleTest,
3330
GoEthereumWeb3ModuleTest,
3431
)
35-
from .utils import (
36-
wait_for_aiohttp,
37-
wait_for_http,
38-
)
39-
40-
41-
@pytest.fixture
42-
def rpc_port():
43-
return get_open_port()
44-
45-
46-
@pytest.fixture
47-
def endpoint_uri(rpc_port):
48-
return f"http://localhost:{rpc_port}"
4932

5033

51-
def _geth_command_arguments(rpc_port, base_geth_command_arguments, geth_version):
34+
def _geth_command_arguments(base_geth_command_arguments, geth_version):
5235
yield from base_geth_command_arguments
5336
if geth_version.major == 1:
5437
yield from (
5538
"--http",
5639
"--http.port",
57-
rpc_port,
40+
"0",
5841
"--http.api",
5942
"admin,debug,eth,net,web3,txpool",
6043
"--ipcdisable",
@@ -64,26 +47,28 @@ def _geth_command_arguments(rpc_port, base_geth_command_arguments, geth_version)
6447

6548

6649
@pytest.fixture
67-
def geth_command_arguments(rpc_port, base_geth_command_arguments, get_geth_version):
68-
return _geth_command_arguments(
69-
rpc_port, base_geth_command_arguments, get_geth_version
70-
)
50+
def geth_command_arguments(base_geth_command_arguments, get_geth_version):
51+
return _geth_command_arguments(base_geth_command_arguments, get_geth_version)
7152

7253

7354
@pytest.fixture
74-
def w3(geth_process, endpoint_uri):
75-
wait_for_http(endpoint_uri)
76-
return Web3(Web3.HTTPProvider(endpoint_uri, request_kwargs={"timeout": 10}))
55+
def w3(start_geth_process_and_yield_port):
56+
port = start_geth_process_and_yield_port
57+
_w3 = Web3(
58+
Web3.HTTPProvider(f"http://127.0.0.1:{port}", request_kwargs={"timeout": 10})
59+
)
60+
return _w3
7761

7862

7963
@pytest.fixture
80-
def auto_w3(geth_process, endpoint_uri):
81-
wait_for_http(endpoint_uri)
82-
64+
def auto_w3(start_geth_process_and_yield_port, monkeypatch):
8365
from web3.auto import (
8466
w3,
8567
)
8668

69+
port = start_geth_process_and_yield_port
70+
monkeypatch.setenv("WEB3_PROVIDER_URI", f"http://127.0.0.1:{port}")
71+
8772
return w3
8873

8974

@@ -116,13 +101,7 @@ class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest):
116101

117102

118103
class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest):
119-
def test_auto_provider_batching(
120-
self,
121-
auto_w3: "Web3",
122-
monkeypatch,
123-
endpoint_uri,
124-
) -> None:
125-
monkeypatch.setenv("WEB3_PROVIDER_URI", endpoint_uri)
104+
def test_auto_provider_batching(self, auto_w3: "Web3") -> None:
126105
# test that batch_requests doesn't error out when using the auto provider
127106
auto_w3.batch_requests()
128107

@@ -139,11 +118,14 @@ class TestGoEthereumTxPoolModuleTest(GoEthereumTxPoolModuleTest):
139118

140119

141120
@pytest_asyncio.fixture
142-
async def async_w3(geth_process, endpoint_uri):
143-
await wait_for_aiohttp(endpoint_uri)
121+
async def async_w3(start_geth_process_and_yield_port):
122+
port = start_geth_process_and_yield_port
144123
_w3 = AsyncWeb3(
145-
AsyncHTTPProvider(endpoint_uri, request_kwargs={"timeout": ClientTimeout(10)})
124+
AsyncHTTPProvider(
125+
f"http://127.0.0.1:{port}", request_kwargs={"timeout": ClientTimeout(10)}
126+
)
146127
)
128+
await _w3.provider.disconnect()
147129
return _w3
148130

149131

tests/integration/go_ethereum/test_goethereum_ipc.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,19 @@ def geth_ipc_path(datadir):
5454

5555

5656
@pytest.fixture
57-
def auto_w3(geth_process, geth_ipc_path):
58-
wait_for_socket(geth_ipc_path)
59-
57+
def auto_w3(start_geth_process_and_yield_port, geth_ipc_path, monkeypatch):
6058
from web3.auto import (
6159
w3,
6260
)
6361

62+
wait_for_socket(geth_ipc_path)
63+
monkeypatch.setenv("WEB3_PROVIDER_URI", f"file:///{geth_ipc_path}")
64+
6465
return w3
6566

6667

6768
@pytest.fixture
68-
def w3(geth_process, geth_ipc_path):
69+
def w3(start_geth_process_and_yield_port, geth_ipc_path):
6970
wait_for_socket(geth_ipc_path)
7071
return Web3(Web3.IPCProvider(geth_ipc_path, timeout=10))
7172

@@ -79,13 +80,7 @@ class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest):
7980

8081

8182
class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest):
82-
def test_auto_provider_batching(
83-
self,
84-
auto_w3: "Web3",
85-
monkeypatch,
86-
geth_ipc_path,
87-
) -> None:
88-
monkeypatch.setenv("WEB3_PROVIDER_URI", f"file:///{geth_ipc_path}")
83+
def test_auto_provider_batching(self, auto_w3: "Web3") -> None:
8984
# test that batch_requests doesn't error out when using the auto provider
9085
auto_w3.batch_requests()
9186

@@ -118,7 +113,7 @@ def test_admin_start_stop_ws(self, w3: "Web3") -> None:
118113

119114

120115
@pytest_asyncio.fixture
121-
async def async_w3(geth_process, geth_ipc_path):
116+
async def async_w3(start_geth_process_and_yield_port, geth_ipc_path):
122117
await wait_for_async_socket(geth_ipc_path)
123118
async with AsyncWeb3(AsyncIPCProvider(geth_ipc_path, request_timeout=10)) as _aw3:
124119
yield _aw3

0 commit comments

Comments
 (0)