1
1
import os
2
2
import re
3
+ import shutil
3
4
import shlex
4
5
import signal
5
6
import subprocess
6
- import time
7
+ import sys
7
8
import threading
9
+ import time
10
+ from bittensor .utils .btlogging import logging
8
11
9
12
import pytest
10
13
from async_substrate_interface import SubstrateInterface
11
14
12
15
from bittensor .core .async_subtensor import AsyncSubtensor
13
16
from bittensor .core .subtensor import Subtensor
14
- from bittensor .utils .btlogging import logging
15
17
from tests .e2e_tests .utils .e2e_test_utils import (
16
18
Templates ,
17
19
setup_wallet ,
18
20
)
19
21
20
22
21
- # Fixture for setting up and tearing down a localnet.sh chain between tests
23
+ def wait_for_node_start (process , timestamp = None ):
24
+ """Waits for node to start in the docker."""
25
+ while True :
26
+ line = process .stdout .readline ()
27
+ if not line :
28
+ break
29
+
30
+ timestamp = timestamp or int (time .time ())
31
+ print (line .strip ())
32
+ # 10 min as timeout
33
+ if int (time .time ()) - timestamp > 20 * 30 :
34
+ print ("Subtensor not started in time" )
35
+ raise TimeoutError
36
+
37
+ pattern = re .compile (r"Imported #1" )
38
+ if pattern .search (line ):
39
+ print ("Node started!" )
40
+ break
41
+
42
+ # Start a background reader after pattern is found
43
+ # To prevent the buffer filling up
44
+ def read_output ():
45
+ while True :
46
+ if not process .stdout .readline ():
47
+ break
48
+
49
+ reader_thread = threading .Thread (target = read_output , daemon = True )
50
+ reader_thread .start ()
51
+
52
+
22
53
@pytest .fixture (scope = "function" )
23
54
def local_chain (request ):
24
- param = request .param if hasattr (request , "param" ) else None
55
+ """Determines whether to run the localnet.sh script in a subprocess or a Docker container."""
56
+ args = request .param if hasattr (request , "param" ) else None
57
+ params = "" if args is None else f"{ args } "
58
+ if shutil .which ("docker" ):
59
+ yield from docker_runner (params )
60
+ return
61
+
62
+ if sys .platform .startswith ("linux" ):
63
+ docker_commend = (
64
+ "Install docker with command "
65
+ "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]"
66
+ )
67
+ elif sys .platform == "darwin" :
68
+ docker_commend = "Install docker with command [blue]brew install docker[/blue]"
69
+ else :
70
+ docker_commend = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]"
71
+
72
+ logging .warning ("Docker not found in the operating system!" )
73
+ logging .warning (docker_commend )
74
+ logging .warning ("Tests are run in legacy mode." )
75
+ yield from legacy_runner (request )
76
+
77
+
78
+ def legacy_runner (params ):
79
+ """Runs the localnet.sh script in a subprocess and waits for it to start."""
25
80
# Get the environment variable for the script path
26
81
script_path = os .getenv ("LOCALNET_SH_PATH" )
27
82
@@ -31,54 +86,20 @@ def local_chain(request):
31
86
pytest .skip ("LOCALNET_SH_PATH environment variable is not set." )
32
87
33
88
# Check if param is None, and handle it accordingly
34
- args = "" if param is None else f"{ param } "
89
+ args = "" if params is None else f"{ params } "
35
90
36
91
# Compile commands to send to process
37
92
cmds = shlex .split (f"{ script_path } { args } " )
38
93
39
- # Pattern match indicates node is compiled and ready
40
- pattern = re .compile (r"Imported #1" )
41
- timestamp = int (time .time ())
42
-
43
- def wait_for_node_start (process , pattern ):
44
- while True :
45
- line = process .stdout .readline ()
46
- if not line :
47
- break
48
-
49
- print (line .strip ())
50
- # 10 min as timeout
51
- if int (time .time ()) - timestamp > 20 * 60 :
52
- print ("Subtensor not started in time" )
53
- raise TimeoutError
54
- if pattern .search (line ):
55
- print ("Node started!" )
56
- break
57
-
58
- # Start a background reader after pattern is found
59
- # To prevent the buffer filling up
60
- def read_output ():
61
- while True :
62
- line = process .stdout .readline ()
63
- if not line :
64
- break
65
-
66
- reader_thread = threading .Thread (target = read_output , daemon = True )
67
- reader_thread .start ()
68
-
69
- env = os .environ .copy ()
70
- env ["BUILD_BINARY" ] = "0"
71
-
72
94
with subprocess .Popen (
73
95
cmds ,
74
- env = env ,
75
96
start_new_session = True ,
76
97
stderr = subprocess .STDOUT ,
77
98
stdout = subprocess .PIPE ,
78
99
text = True ,
79
100
) as process :
80
101
try :
81
- wait_for_node_start (process , pattern )
102
+ wait_for_node_start (process )
82
103
except TimeoutError :
83
104
raise
84
105
else :
@@ -95,6 +116,59 @@ def read_output():
95
116
process .wait ()
96
117
97
118
119
+ def docker_runner (params ):
120
+ """Starts a Docker container before tests and gracefully terminates it after."""
121
+
122
+ container_name = f"test_local_chain_{ str (time .time ()).replace ("." , "_" )} "
123
+ image_name = "ghcr.io/opentensor/subtensor-localnet:latest"
124
+
125
+ # Command to start container
126
+ cmds = [
127
+ "docker" ,
128
+ "run" ,
129
+ "--rm" ,
130
+ "--name" ,
131
+ container_name ,
132
+ "-p" ,
133
+ "9944:9944" ,
134
+ "-p" ,
135
+ "9945:9945" ,
136
+ image_name ,
137
+ params ,
138
+ ]
139
+
140
+ # Start container
141
+ with subprocess .Popen (
142
+ cmds ,
143
+ stdout = subprocess .PIPE ,
144
+ stderr = subprocess .PIPE ,
145
+ text = True ,
146
+ start_new_session = True ,
147
+ ) as process :
148
+ try :
149
+ try :
150
+ wait_for_node_start (process , int (time .time ()))
151
+ except TimeoutError :
152
+ raise
153
+
154
+ result = subprocess .run (
155
+ ["docker" , "ps" , "-q" , "-f" , f"name={ container_name } " ],
156
+ capture_output = True ,
157
+ text = True ,
158
+ )
159
+ if not result .stdout .strip ():
160
+ raise RuntimeError ("Docker container failed to start." )
161
+
162
+ yield SubstrateInterface (url = "ws://127.0.0.1:9944" )
163
+
164
+ finally :
165
+ try :
166
+ subprocess .run (["docker" , "kill" , container_name ])
167
+ process .wait ()
168
+ except subprocess .TimeoutExpired :
169
+ os .killpg (os .getpgid (process .pid ), signal .SIGKILL )
170
+
171
+
98
172
@pytest .fixture (scope = "session" )
99
173
def templates ():
100
174
with Templates () as templates :
0 commit comments