14
14
import time
15
15
import psutil
16
16
17
- def wait_timeout (proc , seconds ):
18
- """Wait for a process to finish, or raise exception after timeout"""
19
- start = time .time ()
20
- end = start + seconds
21
- interval = min (seconds / 1000.0 , .25 )
22
-
23
- while True :
24
- result = proc .poll ()
25
- if result is not None :
26
- return result
27
- if time .time () >= end :
28
- raise RuntimeError ("Process timed out" )
29
- time .sleep (interval )
30
-
31
17
def kill (proc_pid ):
32
18
process = psutil .Process (proc_pid )
33
19
for proc in process .children (recursive = True ):
@@ -54,6 +40,67 @@ def cleanup_processes():
54
40
55
41
testpath = os .path .dirname (__file__ )
56
42
43
+ from threading import Thread
44
+ from Queue import Queue , Empty
45
+
46
+ class NonBlockingStreamReader :
47
+
48
+ def __init__ (self , stream ):
49
+ '''
50
+ stream: the stream to read from.
51
+ Usually a process' stdout or stderr.
52
+ '''
53
+
54
+ self ._s = stream
55
+ self ._q = Queue ()
56
+
57
+ def _populateQueue (stream , queue ):
58
+ '''
59
+ Collect lines from 'stream' and put them in 'quque'.
60
+ '''
61
+
62
+ while True :
63
+ line = stream .readline ()
64
+ if line :
65
+ queue .put (line )
66
+ else :
67
+ break
68
+
69
+ self ._t = Thread (target = _populateQueue ,
70
+ args = (self ._s , self ._q ))
71
+ self ._t .daemon = True
72
+ self ._t .start () #start collecting lines from the stream
73
+
74
+ def readline (self , timeout = None ):
75
+ try :
76
+ return self ._q .get (block = timeout is not None ,
77
+ timeout = timeout )
78
+ except Empty :
79
+ return None
80
+
81
+ class UnexpectedEndOfStream (Exception ):
82
+ pass
83
+
84
+ def wait_timeout (proc , seconds , verbose ):
85
+ """Wait for a process to finish, or raise exception after timeout"""
86
+ start = time .time ()
87
+ end = start + seconds
88
+ interval = min (seconds / 1000.0 , .25 )
89
+
90
+ stdout = NonBlockingStreamReader (proc .stdout )
91
+ while True :
92
+ # Read text in verbose mode. Also sleep for a bit.
93
+ nextline = stdout .readline (interval )
94
+ if nextline and verbose :
95
+ sys .stdout .write (nextline )
96
+ sys .stdout .flush ()
97
+
98
+ # Poll for end of text.
99
+ result = proc .poll ()
100
+ if result is not None :
101
+ return result
102
+ if time .time () >= end :
103
+ raise RuntimeError ("Process timed out" )
57
104
58
105
def wrap_fd (pipeout ):
59
106
# Prepare to pass to child process
@@ -67,7 +114,15 @@ def wrap_fd(pipeout):
67
114
68
115
def lets_run_a_test (name ):
69
116
sitl_args = ['dronekit-sitl' , 'copter-3.3-rc5' , '-I0' , '-S' , '--model' , 'quad' , '--home=-35.363261,149.165230,584,353' ]
117
+
118
+ speedup = os .environ .get ('TEST_SPEEDUP' , '1' )
119
+ rate = os .environ .get ('TEST_RATE' , '200' )
120
+ sitl_args += ['--speedup' , str (speedup ), '-r' , str (rate )]
121
+
122
+ # Change CPU core affinity.
123
+ # TODO change affinity on osx/linux
70
124
if sys .platform == 'win32' :
125
+ # 0x14 = 0b1110 = all cores except cpu 1
71
126
sitl = Popen (['start' , '/affinity' , '14' , '/realtime' , '/b' , '/wait' ] + sitl_args , shell = True , stdout = PIPE , stderr = PIPE )
72
127
else :
73
128
sitl = Popen (sitl_args , stdout = PIPE , stderr = PIPE )
@@ -104,36 +159,41 @@ def lets_run_a_test(name):
104
159
sys .stdout .flush ()
105
160
sys .stderr .flush ()
106
161
107
- # APPVEYOR = SLOW
108
- timeout = 15 * 60 if sys .platform == 'win32' else 5 * 60
162
+ mavproxy_verbose = os .environ .get ('TEST_VERBOSE' , '0' ) != '0'
163
+ timeout = 5 * 60
164
+
109
165
try :
110
- p = Popen ([sys .executable , '-m' , 'MAVProxy.mavproxy' , '--logfile=' + tempfile .mkstemp ()[1 ], '--master=tcp:127.0.0.1:5760' ], cwd = testpath , env = newenv , stdin = PIPE , stdout = PIPE )#, stderr=PIPE)
166
+ p = Popen ([sys .executable , '-m' , 'MAVProxy.mavproxy' , '--logfile=' + tempfile .mkstemp ()[1 ], '--master=tcp:127.0.0.1:5760' ],
167
+ cwd = testpath , env = newenv , stdin = PIPE , stdout = PIPE ,
168
+ stderr = PIPE if not mavproxy_verbose else None )
111
169
bg .append (p )
112
170
171
+ # TODO this sleep is only for us to waiting until
172
+ # all parameters to be received; would prefer to
173
+ # move this to testlib.py and happen asap
113
174
while p .poll () == None :
114
175
line = p .stdout .readline ()
115
- sys .stdout .write (line )
116
- sys .stdout .flush ()
176
+ if mavproxy_verbose :
177
+ sys .stdout .write (line )
178
+ sys .stdout .flush ()
117
179
if 'parameters' in line :
118
180
break
119
181
120
- # TODO this sleep is only for us to waiting until
121
- # all parameters to be received; would prefer to
122
- # move this to testlib.py and happen asap
123
182
time .sleep (3 )
124
- p .stdin .write ('module load droneapi.module.api\n ' )
183
+
184
+ # NOTE these are *very inappropriate settings*
185
+ # to make on a real vehicle. They are leveraged
186
+ # exclusively for simulation. Take heed!!!
125
187
p .stdin .write ('param set ARMING_CHECK 0\n ' )
188
+ p .stdin .write ('param set FS_THR_ENABLE 0\n ' )
189
+ p .stdin .write ('param set FS_GCS_ENABLE 0\n ' )
190
+ p .stdin .write ('param set EKF_CHECK_THRESH 0\n ' )
191
+
192
+ p .stdin .write ('module load droneapi.module.api\n ' )
126
193
p .stdin .write ('api start testlib.py\n ' )
127
194
p .stdin .flush ()
128
195
129
- while True :
130
- nextline = p .stdout .readline ()
131
- if nextline == '' and p .poll () != None :
132
- break
133
- sys .stdout .write (nextline )
134
- sys .stdout .flush ()
135
-
136
- # wait_timeout(p, timeout)
196
+ wait_timeout (p , timeout , mavproxy_verbose )
137
197
except RuntimeError :
138
198
kill (p .pid )
139
199
p .returncode = 143
@@ -148,17 +208,31 @@ def lets_run_a_test(name):
148
208
bg .remove (sitl )
149
209
150
210
if p .returncode != 0 :
151
- print ('[runner] ...aborting with dronekit error code ' + str (p .returncode ))
152
- sys .stdout .flush ()
153
- sys .stderr .flush ()
154
- sys .exit (p .returncode )
155
-
156
- print ('[runner] ...success.' )
157
- time .sleep (5 )
211
+ print ('[runner] ...failed with dronekit error code ' + str (p .returncode ))
212
+ else :
213
+ print ('[runner] ...success.' )
158
214
159
- for i in os .listdir (testpath ):
160
- if i .startswith ('test_' ) and i .endswith ('.py' ):
161
- lets_run_a_test (i [:- 3 ])
215
+ sys .stdout .flush ()
216
+ sys .stderr .flush ()
217
+ time .sleep (5 )
218
+ return p .returncode
219
+
220
+ retry = int (os .environ .get ('TEST_RETRY' , '1' ))
221
+ filelist = sys .argv [1 :] if len (sys .argv ) > 1 else os .listdir (testpath )
222
+ for path in filelist :
223
+ assert os .path .isfile (os .path .join (testpath , path )), '"%s" is not a valid test file' % (path ,)
224
+ if path .startswith ('test_' ) and path .endswith ('.py' ):
225
+ name = path [:- 3 ]
226
+ i = retry
227
+ while True :
228
+ ret = lets_run_a_test (name )
229
+ if ret == 0 :
230
+ break
231
+ i = i - 1
232
+ if i == 0 :
233
+ print ('[runner] aborting after failed test.' )
234
+ sys .exit (ret )
235
+ print ('[runner] retrying %s %s more times' % (name , i , ))
162
236
163
237
print ('[runner] finished.' )
164
238
sys .stdout .flush ()
0 commit comments