5
5
import shutil
6
6
import signal
7
7
import subprocess
8
+ import sys
8
9
import time
9
10
10
11
import pytest
13
14
from .utils import setup_wallet
14
15
15
16
17
+ def wait_for_node_start (process , pattern , timestamp : int = None ):
18
+ for line in process .stdout :
19
+ print (line .strip ())
20
+ # 20 min as timeout
21
+ timestamp = timestamp or int (time .time ())
22
+ if int (time .time ()) - timestamp > 20 * 60 :
23
+ pytest .fail ("Subtensor not started in time" )
24
+ if pattern .search (line ):
25
+ print ("Node started!" )
26
+ break
27
+
28
+
16
29
# Fixture for setting up and tearing down a localnet.sh chain between tests
17
30
@pytest .fixture (scope = "function" )
18
31
def local_chain (request ):
32
+ """Determines whether to run the localnet.sh script in a subprocess or a Docker container."""
33
+ args = request .param if hasattr (request , "param" ) else None
34
+ params = "" if args is None else f"{ args } "
35
+ if shutil .which ("docker" ) and not os .getenv ("USE_DOCKER" ) == "0" :
36
+ yield from docker_runner (params )
37
+ else :
38
+ if not os .getenv ("USE_DOCKER" ) == "0" :
39
+ if sys .platform .startswith ("linux" ):
40
+ docker_command = (
41
+ "Install docker with command "
42
+ "[blue]sudo apt-get update && sudo apt-get install docker.io -y[/blue]"
43
+ " or use documentation [blue]https://docs.docker.com/engine/install/[/blue]"
44
+ )
45
+ elif sys .platform == "darwin" :
46
+ docker_command = (
47
+ "Install docker with command [blue]brew install docker[/blue]"
48
+ )
49
+ else :
50
+ docker_command = "[blue]Unknown OS, install Docker manually: https://docs.docker.com/get-docker/[/blue]"
51
+
52
+ logging .warning ("Docker not found in the operating system!" )
53
+ logging .warning (docker_command )
54
+ logging .warning ("Tests are run in legacy mode." )
55
+ yield from legacy_runner (request )
56
+
57
+
58
+ def legacy_runner (request ):
19
59
param = request .param if hasattr (request , "param" ) else None
20
60
# Get the environment variable for the script path
21
61
script_path = os .getenv ("LOCALNET_SH_PATH" )
@@ -41,18 +81,6 @@ def local_chain(request):
41
81
# Install neuron templates
42
82
logging .info ("Downloading and installing neuron templates from github" )
43
83
44
- timestamp = int (time .time ())
45
-
46
- def wait_for_node_start (process , pattern ):
47
- for line in process .stdout :
48
- print (line .strip ())
49
- # 20 min as timeout
50
- if int (time .time ()) - timestamp > 20 * 60 :
51
- pytest .fail ("Subtensor not started in time" )
52
- if pattern .search (line ):
53
- print ("Node started!" )
54
- break
55
-
56
84
wait_for_node_start (process , pattern )
57
85
58
86
# Run the test, passing in substrate interface
@@ -72,6 +100,108 @@ def wait_for_node_start(process, pattern):
72
100
process .wait ()
73
101
74
102
103
+ def docker_runner (params ):
104
+ """Starts a Docker container before tests and gracefully terminates it after."""
105
+
106
+ def is_docker_running ():
107
+ """Check if Docker has been run."""
108
+ try :
109
+ subprocess .run (
110
+ ["docker" , "info" ],
111
+ stdout = subprocess .DEVNULL ,
112
+ stderr = subprocess .DEVNULL ,
113
+ check = True ,
114
+ )
115
+ return True
116
+ except subprocess .CalledProcessError :
117
+ return False
118
+
119
+ def try_start_docker ():
120
+ """Run docker based on OS."""
121
+ try :
122
+ subprocess .run (["open" , "-a" , "Docker" ], check = True ) # macOS
123
+ except (FileNotFoundError , subprocess .CalledProcessError ):
124
+ try :
125
+ subprocess .run (["systemctl" , "start" , "docker" ], check = True ) # Linux
126
+ except (FileNotFoundError , subprocess .CalledProcessError ):
127
+ try :
128
+ subprocess .run (
129
+ ["sudo" , "service" , "docker" , "start" ], check = True
130
+ ) # Linux alternative
131
+ except (FileNotFoundError , subprocess .CalledProcessError ):
132
+ print ("Failed to start Docker. Manual start may be required." )
133
+ return False
134
+
135
+ # Wait Docker run 10 attempts with 3 sec waits
136
+ for _ in range (10 ):
137
+ if is_docker_running ():
138
+ return True
139
+ time .sleep (3 )
140
+
141
+ print ("Docker wasn't run. Manual start may be required." )
142
+ return False
143
+
144
+ container_name = f"test_local_chain_{ str (time .time ()).replace ('.' , '_' )} "
145
+ image_name = "ghcr.io/opentensor/subtensor-localnet:latest"
146
+
147
+ # Command to start container
148
+ cmds = [
149
+ "docker" ,
150
+ "run" ,
151
+ "--rm" ,
152
+ "--name" ,
153
+ container_name ,
154
+ "-p" ,
155
+ "9944:9944" ,
156
+ "-p" ,
157
+ "9945:9945" ,
158
+ image_name ,
159
+ params ,
160
+ ]
161
+
162
+ try_start_docker ()
163
+
164
+ # Start container
165
+ with subprocess .Popen (
166
+ cmds ,
167
+ stdout = subprocess .PIPE ,
168
+ stderr = subprocess .PIPE ,
169
+ text = True ,
170
+ start_new_session = True ,
171
+ ) as process :
172
+ try :
173
+ substrate = None
174
+ try :
175
+ pattern = re .compile (r"Imported #1" )
176
+ wait_for_node_start (process , pattern , int (time .time ()))
177
+ except TimeoutError :
178
+ raise
179
+
180
+ result = subprocess .run (
181
+ ["docker" , "ps" , "-q" , "-f" , f"name={ container_name } " ],
182
+ capture_output = True ,
183
+ text = True ,
184
+ )
185
+ if not result .stdout .strip ():
186
+ raise RuntimeError ("Docker container failed to start." )
187
+
188
+ substrate = AsyncSubstrateInterface (url = "ws://127.0.0.1:9944" )
189
+ yield substrate
190
+
191
+ finally :
192
+ try :
193
+ if substrate :
194
+ substrate .close ()
195
+ except Exception :
196
+ pass
197
+
198
+ try :
199
+ subprocess .run (["docker" , "kill" , container_name ])
200
+ process .wait (timeout = 10 )
201
+ except subprocess .TimeoutExpired :
202
+ os .killpg (os .getpgid (process .pid ), signal .SIGKILL )
203
+
204
+
75
205
@pytest .fixture (scope = "function" )
76
206
def wallet_setup ():
77
207
wallet_paths = []
0 commit comments