1414import time
1515import psutil
1616
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-
3117def kill (proc_pid ):
3218 process = psutil .Process (proc_pid )
3319 for proc in process .children (recursive = True ):
@@ -54,6 +40,67 @@ def cleanup_processes():
5440
5541testpath = os .path .dirname (__file__ )
5642
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" )
57104
58105def wrap_fd (pipeout ):
59106 # Prepare to pass to child process
@@ -67,7 +114,15 @@ def wrap_fd(pipeout):
67114
68115def lets_run_a_test (name ):
69116 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
70124 if sys .platform == 'win32' :
125+ # 0x14 = 0b1110 = all cores except cpu 1
71126 sitl = Popen (['start' , '/affinity' , '14' , '/realtime' , '/b' , '/wait' ] + sitl_args , shell = True , stdout = PIPE , stderr = PIPE )
72127 else :
73128 sitl = Popen (sitl_args , stdout = PIPE , stderr = PIPE )
@@ -104,36 +159,41 @@ def lets_run_a_test(name):
104159 sys .stdout .flush ()
105160 sys .stderr .flush ()
106161
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+
109165 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 )
111169 bg .append (p )
112170
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
113174 while p .poll () == None :
114175 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 ()
117179 if 'parameters' in line :
118180 break
119181
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
123182 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!!!
125187 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 ' )
126193 p .stdin .write ('api start testlib.py\n ' )
127194 p .stdin .flush ()
128195
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 )
137197 except RuntimeError :
138198 kill (p .pid )
139199 p .returncode = 143
@@ -148,17 +208,31 @@ def lets_run_a_test(name):
148208 bg .remove (sitl )
149209
150210 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.' )
158214
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 , ))
162236
163237print ('[runner] finished.' )
164238sys .stdout .flush ()
0 commit comments