|
1 #!/usr/bin/env python |
|
2 |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 # You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 import optparse |
|
8 import os |
|
9 import subprocess |
|
10 import sys |
|
11 import unittest |
|
12 from mozprocess import processhandler |
|
13 from time import sleep |
|
14 |
|
15 here = os.path.dirname(os.path.abspath(__file__)) |
|
16 |
|
17 def make_proclaunch(aDir): |
|
18 """ |
|
19 Makes the proclaunch executable. |
|
20 Params: |
|
21 aDir - the directory in which to issue the make commands |
|
22 Returns: |
|
23 the path to the proclaunch executable that is generated |
|
24 """ |
|
25 |
|
26 if sys.platform == "win32": |
|
27 exepath = os.path.join(aDir, "proclaunch.exe") |
|
28 else: |
|
29 exepath = os.path.join(aDir, "proclaunch") |
|
30 |
|
31 # remove the launcher, if it already exists |
|
32 # otherwise, if the make fails you may not notice |
|
33 if os.path.exists(exepath): |
|
34 os.remove(exepath) |
|
35 |
|
36 # Ideally make should take care of both calls through recursion, but since it doesn't, |
|
37 # on windows anyway (to file?), let's just call out both targets explicitly. |
|
38 for command in [["make", "-C", "iniparser"], |
|
39 ["make"]]: |
|
40 process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=aDir) |
|
41 stdout, stderr = process.communicate() |
|
42 if process.returncode: |
|
43 # SomethingBadHappen; print all the things |
|
44 print "%s: exit %d" % (command, process.returncode) |
|
45 print "stdout:\n%s" % stdout |
|
46 print "stderr:\n%s" % stderr |
|
47 raise subprocess.CalledProcessError(process.returncode, command, stdout) |
|
48 |
|
49 # ensure the launcher now exists |
|
50 if not os.path.exists(exepath): |
|
51 raise AssertionError("proclaunch executable '%s' does not exist (sys.platform=%s)" % (exepath, sys.platform)) |
|
52 return exepath |
|
53 |
|
54 def check_for_process(processName): |
|
55 """ |
|
56 Use to determine if process of the given name is still running. |
|
57 |
|
58 Returns: |
|
59 detected -- True if process is detected to exist, False otherwise |
|
60 output -- if process exists, stdout of the process, '' otherwise |
|
61 """ |
|
62 # TODO: replace with |
|
63 # https://github.com/mozilla/mozbase/blob/master/mozprocess/mozprocess/pid.py |
|
64 # which should be augmented from talos |
|
65 # see https://bugzilla.mozilla.org/show_bug.cgi?id=705864 |
|
66 output = '' |
|
67 if sys.platform == "win32": |
|
68 # On windows we use tasklist |
|
69 p1 = subprocess.Popen(["tasklist"], stdout=subprocess.PIPE) |
|
70 output = p1.communicate()[0] |
|
71 detected = False |
|
72 for line in output.splitlines(): |
|
73 if processName in line: |
|
74 detected = True |
|
75 break |
|
76 else: |
|
77 p1 = subprocess.Popen(["ps", "-ef"], stdout=subprocess.PIPE) |
|
78 p2 = subprocess.Popen(["grep", processName], stdin=p1.stdout, stdout=subprocess.PIPE) |
|
79 p1.stdout.close() |
|
80 output = p2.communicate()[0] |
|
81 detected = False |
|
82 for line in output.splitlines(): |
|
83 if "grep %s" % processName in line: |
|
84 continue |
|
85 elif processName in line and not 'defunct' in line: |
|
86 detected = True |
|
87 break |
|
88 |
|
89 return detected, output |
|
90 |
|
91 |
|
92 class ProcTest(unittest.TestCase): |
|
93 |
|
94 # whether to remove created files on exit |
|
95 cleanup = os.environ.get('CLEANUP', 'true').lower() in ('1', 'true') |
|
96 |
|
97 @classmethod |
|
98 def setUpClass(cls): |
|
99 cls.proclaunch = make_proclaunch(here) |
|
100 |
|
101 @classmethod |
|
102 def tearDownClass(cls): |
|
103 del cls.proclaunch |
|
104 if not cls.cleanup: |
|
105 return |
|
106 files = [('proclaunch',), |
|
107 ('proclaunch.exe',), |
|
108 ('iniparser', 'dictionary.o'), |
|
109 ('iniparser', 'iniparser.lib'), |
|
110 ('iniparser', 'iniparser.o'), |
|
111 ('iniparser', 'libiniparser.a'), |
|
112 ('iniparser', 'libiniparser.so.0'), |
|
113 ] |
|
114 files = [os.path.join(here, *path) for path in files] |
|
115 errors = [] |
|
116 for path in files: |
|
117 if os.path.exists(path): |
|
118 try: |
|
119 os.remove(path) |
|
120 except OSError as e: |
|
121 errors.append(str(e)) |
|
122 if errors: |
|
123 raise OSError("Error(s) encountered tearing down %s.%s:\n%s" % (cls.__module__, cls.__name__, '\n'.join(errors))) |
|
124 |
|
125 def test_process_normal_finish(self): |
|
126 """Process is started, runs to completion while we wait for it""" |
|
127 |
|
128 p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], |
|
129 cwd=here) |
|
130 p.run() |
|
131 p.wait() |
|
132 |
|
133 detected, output = check_for_process(self.proclaunch) |
|
134 self.determine_status(detected, |
|
135 output, |
|
136 p.proc.returncode, |
|
137 p.didTimeout) |
|
138 |
|
139 def test_commandline_no_args(self): |
|
140 """Command line is reported correctly when no arguments are specified""" |
|
141 p = processhandler.ProcessHandler(self.proclaunch, cwd=here) |
|
142 self.assertEqual(p.commandline, self.proclaunch) |
|
143 |
|
144 def test_commandline_overspecified(self): |
|
145 """Command line raises an exception when the arguments are specified ambiguously""" |
|
146 err = None |
|
147 try: |
|
148 p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], |
|
149 args=["1", "2", "3"], |
|
150 cwd=here) |
|
151 except TypeError, e: |
|
152 err = e |
|
153 |
|
154 self.assertTrue(err) |
|
155 |
|
156 def test_commandline_from_list(self): |
|
157 """Command line is reported correctly when command and arguments are specified in a list""" |
|
158 p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], |
|
159 cwd=here) |
|
160 self.assertEqual(p.commandline, self.proclaunch + ' process_normal_finish.ini') |
|
161 |
|
162 def test_commandline_over_specified(self): |
|
163 """Command line raises an exception when the arguments are specified ambiguously""" |
|
164 err = None |
|
165 try: |
|
166 p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], |
|
167 args=["1", "2", "3"], |
|
168 cwd=here) |
|
169 except TypeError, e: |
|
170 err = e |
|
171 |
|
172 self.assertTrue(err) |
|
173 |
|
174 def test_commandline_from_args(self): |
|
175 """Command line is reported correctly when arguments are specified in a dedicated list""" |
|
176 p = processhandler.ProcessHandler(self.proclaunch, |
|
177 args=["1", "2", "3"], |
|
178 cwd=here) |
|
179 self.assertEqual(p.commandline, self.proclaunch + ' 1 2 3') |
|
180 |
|
181 def test_process_wait(self): |
|
182 """Process is started runs to completion while we wait indefinitely""" |
|
183 |
|
184 p = processhandler.ProcessHandler([self.proclaunch, |
|
185 "process_waittimeout_10s.ini"], |
|
186 cwd=here) |
|
187 p.run() |
|
188 p.wait() |
|
189 |
|
190 detected, output = check_for_process(self.proclaunch) |
|
191 self.determine_status(detected, |
|
192 output, |
|
193 p.proc.returncode, |
|
194 p.didTimeout) |
|
195 |
|
196 def test_process_timeout(self): |
|
197 """ Process is started, runs but we time out waiting on it |
|
198 to complete |
|
199 """ |
|
200 p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"], |
|
201 cwd=here) |
|
202 p.run(timeout=10) |
|
203 p.wait() |
|
204 |
|
205 detected, output = check_for_process(self.proclaunch) |
|
206 self.determine_status(detected, |
|
207 output, |
|
208 p.proc.returncode, |
|
209 p.didTimeout, |
|
210 False, |
|
211 ['returncode', 'didtimeout']) |
|
212 |
|
213 def test_process_timeout_no_kill(self): |
|
214 """ Process is started, runs but we time out waiting on it |
|
215 to complete. Process should not be killed. |
|
216 """ |
|
217 p = None |
|
218 def timeout_handler(): |
|
219 self.assertEqual(p.proc.poll(), None) |
|
220 p.kill() |
|
221 p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"], |
|
222 cwd=here, |
|
223 onTimeout=(timeout_handler,), |
|
224 kill_on_timeout=False) |
|
225 p.run(timeout=1) |
|
226 p.wait() |
|
227 self.assertTrue(p.didTimeout) |
|
228 |
|
229 detected, output = check_for_process(self.proclaunch) |
|
230 self.determine_status(detected, |
|
231 output, |
|
232 p.proc.returncode, |
|
233 p.didTimeout, |
|
234 False, |
|
235 ['returncode', 'didtimeout']) |
|
236 |
|
237 def test_process_waittimeout(self): |
|
238 """ |
|
239 Process is started, then wait is called and times out. |
|
240 Process is still running and didn't timeout |
|
241 """ |
|
242 p = processhandler.ProcessHandler([self.proclaunch, |
|
243 "process_waittimeout_10s.ini"], |
|
244 cwd=here) |
|
245 |
|
246 p.run() |
|
247 p.wait(timeout=5) |
|
248 |
|
249 detected, output = check_for_process(self.proclaunch) |
|
250 self.determine_status(detected, |
|
251 output, |
|
252 p.proc.returncode, |
|
253 p.didTimeout, |
|
254 True, |
|
255 ()) |
|
256 |
|
257 def test_process_waitnotimeout(self): |
|
258 """ Process is started, runs to completion before our wait times out |
|
259 """ |
|
260 p = processhandler.ProcessHandler([self.proclaunch, |
|
261 "process_waittimeout_10s.ini"], |
|
262 cwd=here) |
|
263 p.run(timeout=30) |
|
264 p.wait() |
|
265 |
|
266 detected, output = check_for_process(self.proclaunch) |
|
267 self.determine_status(detected, |
|
268 output, |
|
269 p.proc.returncode, |
|
270 p.didTimeout) |
|
271 |
|
272 def test_process_kill(self): |
|
273 """Process is started, we kill it""" |
|
274 |
|
275 p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], |
|
276 cwd=here) |
|
277 p.run() |
|
278 p.kill() |
|
279 |
|
280 detected, output = check_for_process(self.proclaunch) |
|
281 self.determine_status(detected, |
|
282 output, |
|
283 p.proc.returncode, |
|
284 p.didTimeout) |
|
285 |
|
286 def test_process_output_twice(self): |
|
287 """ |
|
288 Process is started, then processOutput is called a second time explicitly |
|
289 """ |
|
290 p = processhandler.ProcessHandler([self.proclaunch, |
|
291 "process_waittimeout_10s.ini"], |
|
292 cwd=here) |
|
293 |
|
294 p.run() |
|
295 p.processOutput(timeout=5) |
|
296 p.wait() |
|
297 |
|
298 detected, output = check_for_process(self.proclaunch) |
|
299 self.determine_status(detected, |
|
300 output, |
|
301 p.proc.returncode, |
|
302 p.didTimeout, |
|
303 False, |
|
304 ()) |
|
305 |
|
306 def determine_status(self, |
|
307 detected=False, |
|
308 output='', |
|
309 returncode=0, |
|
310 didtimeout=False, |
|
311 isalive=False, |
|
312 expectedfail=()): |
|
313 """ |
|
314 Use to determine if the situation has failed. |
|
315 Parameters: |
|
316 detected -- value from check_for_process to determine if the process is detected |
|
317 output -- string of data from detected process, can be '' |
|
318 returncode -- return code from process, defaults to 0 |
|
319 didtimeout -- True if process timed out, defaults to False |
|
320 isalive -- Use True to indicate we pass if the process exists; however, by default |
|
321 the test will pass if the process does not exist (isalive == False) |
|
322 expectedfail -- Defaults to [], used to indicate a list of fields that are expected to fail |
|
323 """ |
|
324 if 'returncode' in expectedfail: |
|
325 self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode) |
|
326 elif not isalive: |
|
327 self.assertTrue(returncode == 0, "Detected non-zero return code of: %d" % returncode) |
|
328 |
|
329 if 'didtimeout' in expectedfail: |
|
330 self.assertTrue(didtimeout, "Detected that process didn't time out") |
|
331 else: |
|
332 self.assertTrue(not didtimeout, "Detected that process timed out") |
|
333 |
|
334 if isalive: |
|
335 self.assertTrue(detected, "Detected process is not running, process output: %s" % output) |
|
336 else: |
|
337 self.assertTrue(not detected, "Detected process is still running, process output: %s" % output) |
|
338 |
|
339 if __name__ == '__main__': |
|
340 unittest.main() |