michael@0: #!/usr/bin/env python michael@0: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: # You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import optparse michael@0: import os michael@0: import subprocess michael@0: import sys michael@0: import unittest michael@0: from mozprocess import processhandler michael@0: from time import sleep michael@0: michael@0: here = os.path.dirname(os.path.abspath(__file__)) michael@0: michael@0: def make_proclaunch(aDir): michael@0: """ michael@0: Makes the proclaunch executable. michael@0: Params: michael@0: aDir - the directory in which to issue the make commands michael@0: Returns: michael@0: the path to the proclaunch executable that is generated michael@0: """ michael@0: michael@0: if sys.platform == "win32": michael@0: exepath = os.path.join(aDir, "proclaunch.exe") michael@0: else: michael@0: exepath = os.path.join(aDir, "proclaunch") michael@0: michael@0: # remove the launcher, if it already exists michael@0: # otherwise, if the make fails you may not notice michael@0: if os.path.exists(exepath): michael@0: os.remove(exepath) michael@0: michael@0: # Ideally make should take care of both calls through recursion, but since it doesn't, michael@0: # on windows anyway (to file?), let's just call out both targets explicitly. michael@0: for command in [["make", "-C", "iniparser"], michael@0: ["make"]]: michael@0: process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=aDir) michael@0: stdout, stderr = process.communicate() michael@0: if process.returncode: michael@0: # SomethingBadHappen; print all the things michael@0: print "%s: exit %d" % (command, process.returncode) michael@0: print "stdout:\n%s" % stdout michael@0: print "stderr:\n%s" % stderr michael@0: raise subprocess.CalledProcessError(process.returncode, command, stdout) michael@0: michael@0: # ensure the launcher now exists michael@0: if not os.path.exists(exepath): michael@0: raise AssertionError("proclaunch executable '%s' does not exist (sys.platform=%s)" % (exepath, sys.platform)) michael@0: return exepath michael@0: michael@0: def check_for_process(processName): michael@0: """ michael@0: Use to determine if process of the given name is still running. michael@0: michael@0: Returns: michael@0: detected -- True if process is detected to exist, False otherwise michael@0: output -- if process exists, stdout of the process, '' otherwise michael@0: """ michael@0: # TODO: replace with michael@0: # https://github.com/mozilla/mozbase/blob/master/mozprocess/mozprocess/pid.py michael@0: # which should be augmented from talos michael@0: # see https://bugzilla.mozilla.org/show_bug.cgi?id=705864 michael@0: output = '' michael@0: if sys.platform == "win32": michael@0: # On windows we use tasklist michael@0: p1 = subprocess.Popen(["tasklist"], stdout=subprocess.PIPE) michael@0: output = p1.communicate()[0] michael@0: detected = False michael@0: for line in output.splitlines(): michael@0: if processName in line: michael@0: detected = True michael@0: break michael@0: else: michael@0: p1 = subprocess.Popen(["ps", "-ef"], stdout=subprocess.PIPE) michael@0: p2 = subprocess.Popen(["grep", processName], stdin=p1.stdout, stdout=subprocess.PIPE) michael@0: p1.stdout.close() michael@0: output = p2.communicate()[0] michael@0: detected = False michael@0: for line in output.splitlines(): michael@0: if "grep %s" % processName in line: michael@0: continue michael@0: elif processName in line and not 'defunct' in line: michael@0: detected = True michael@0: break michael@0: michael@0: return detected, output michael@0: michael@0: michael@0: class ProcTest(unittest.TestCase): michael@0: michael@0: # whether to remove created files on exit michael@0: cleanup = os.environ.get('CLEANUP', 'true').lower() in ('1', 'true') michael@0: michael@0: @classmethod michael@0: def setUpClass(cls): michael@0: cls.proclaunch = make_proclaunch(here) michael@0: michael@0: @classmethod michael@0: def tearDownClass(cls): michael@0: del cls.proclaunch michael@0: if not cls.cleanup: michael@0: return michael@0: files = [('proclaunch',), michael@0: ('proclaunch.exe',), michael@0: ('iniparser', 'dictionary.o'), michael@0: ('iniparser', 'iniparser.lib'), michael@0: ('iniparser', 'iniparser.o'), michael@0: ('iniparser', 'libiniparser.a'), michael@0: ('iniparser', 'libiniparser.so.0'), michael@0: ] michael@0: files = [os.path.join(here, *path) for path in files] michael@0: errors = [] michael@0: for path in files: michael@0: if os.path.exists(path): michael@0: try: michael@0: os.remove(path) michael@0: except OSError as e: michael@0: errors.append(str(e)) michael@0: if errors: michael@0: raise OSError("Error(s) encountered tearing down %s.%s:\n%s" % (cls.__module__, cls.__name__, '\n'.join(errors))) michael@0: michael@0: def test_process_normal_finish(self): michael@0: """Process is started, runs to completion while we wait for it""" michael@0: michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], michael@0: cwd=here) michael@0: p.run() michael@0: p.wait() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout) michael@0: michael@0: def test_commandline_no_args(self): michael@0: """Command line is reported correctly when no arguments are specified""" michael@0: p = processhandler.ProcessHandler(self.proclaunch, cwd=here) michael@0: self.assertEqual(p.commandline, self.proclaunch) michael@0: michael@0: def test_commandline_overspecified(self): michael@0: """Command line raises an exception when the arguments are specified ambiguously""" michael@0: err = None michael@0: try: michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], michael@0: args=["1", "2", "3"], michael@0: cwd=here) michael@0: except TypeError, e: michael@0: err = e michael@0: michael@0: self.assertTrue(err) michael@0: michael@0: def test_commandline_from_list(self): michael@0: """Command line is reported correctly when command and arguments are specified in a list""" michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], michael@0: cwd=here) michael@0: self.assertEqual(p.commandline, self.proclaunch + ' process_normal_finish.ini') michael@0: michael@0: def test_commandline_over_specified(self): michael@0: """Command line raises an exception when the arguments are specified ambiguously""" michael@0: err = None michael@0: try: michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], michael@0: args=["1", "2", "3"], michael@0: cwd=here) michael@0: except TypeError, e: michael@0: err = e michael@0: michael@0: self.assertTrue(err) michael@0: michael@0: def test_commandline_from_args(self): michael@0: """Command line is reported correctly when arguments are specified in a dedicated list""" michael@0: p = processhandler.ProcessHandler(self.proclaunch, michael@0: args=["1", "2", "3"], michael@0: cwd=here) michael@0: self.assertEqual(p.commandline, self.proclaunch + ' 1 2 3') michael@0: michael@0: def test_process_wait(self): michael@0: """Process is started runs to completion while we wait indefinitely""" michael@0: michael@0: p = processhandler.ProcessHandler([self.proclaunch, michael@0: "process_waittimeout_10s.ini"], michael@0: cwd=here) michael@0: p.run() michael@0: p.wait() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout) michael@0: michael@0: def test_process_timeout(self): michael@0: """ Process is started, runs but we time out waiting on it michael@0: to complete michael@0: """ michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"], michael@0: cwd=here) michael@0: p.run(timeout=10) michael@0: p.wait() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout, michael@0: False, michael@0: ['returncode', 'didtimeout']) michael@0: michael@0: def test_process_timeout_no_kill(self): michael@0: """ Process is started, runs but we time out waiting on it michael@0: to complete. Process should not be killed. michael@0: """ michael@0: p = None michael@0: def timeout_handler(): michael@0: self.assertEqual(p.proc.poll(), None) michael@0: p.kill() michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"], michael@0: cwd=here, michael@0: onTimeout=(timeout_handler,), michael@0: kill_on_timeout=False) michael@0: p.run(timeout=1) michael@0: p.wait() michael@0: self.assertTrue(p.didTimeout) michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout, michael@0: False, michael@0: ['returncode', 'didtimeout']) michael@0: michael@0: def test_process_waittimeout(self): michael@0: """ michael@0: Process is started, then wait is called and times out. michael@0: Process is still running and didn't timeout michael@0: """ michael@0: p = processhandler.ProcessHandler([self.proclaunch, michael@0: "process_waittimeout_10s.ini"], michael@0: cwd=here) michael@0: michael@0: p.run() michael@0: p.wait(timeout=5) michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout, michael@0: True, michael@0: ()) michael@0: michael@0: def test_process_waitnotimeout(self): michael@0: """ Process is started, runs to completion before our wait times out michael@0: """ michael@0: p = processhandler.ProcessHandler([self.proclaunch, michael@0: "process_waittimeout_10s.ini"], michael@0: cwd=here) michael@0: p.run(timeout=30) michael@0: p.wait() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout) michael@0: michael@0: def test_process_kill(self): michael@0: """Process is started, we kill it""" michael@0: michael@0: p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"], michael@0: cwd=here) michael@0: p.run() michael@0: p.kill() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout) michael@0: michael@0: def test_process_output_twice(self): michael@0: """ michael@0: Process is started, then processOutput is called a second time explicitly michael@0: """ michael@0: p = processhandler.ProcessHandler([self.proclaunch, michael@0: "process_waittimeout_10s.ini"], michael@0: cwd=here) michael@0: michael@0: p.run() michael@0: p.processOutput(timeout=5) michael@0: p.wait() michael@0: michael@0: detected, output = check_for_process(self.proclaunch) michael@0: self.determine_status(detected, michael@0: output, michael@0: p.proc.returncode, michael@0: p.didTimeout, michael@0: False, michael@0: ()) michael@0: michael@0: def determine_status(self, michael@0: detected=False, michael@0: output='', michael@0: returncode=0, michael@0: didtimeout=False, michael@0: isalive=False, michael@0: expectedfail=()): michael@0: """ michael@0: Use to determine if the situation has failed. michael@0: Parameters: michael@0: detected -- value from check_for_process to determine if the process is detected michael@0: output -- string of data from detected process, can be '' michael@0: returncode -- return code from process, defaults to 0 michael@0: didtimeout -- True if process timed out, defaults to False michael@0: isalive -- Use True to indicate we pass if the process exists; however, by default michael@0: the test will pass if the process does not exist (isalive == False) michael@0: expectedfail -- Defaults to [], used to indicate a list of fields that are expected to fail michael@0: """ michael@0: if 'returncode' in expectedfail: michael@0: self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode) michael@0: elif not isalive: michael@0: self.assertTrue(returncode == 0, "Detected non-zero return code of: %d" % returncode) michael@0: michael@0: if 'didtimeout' in expectedfail: michael@0: self.assertTrue(didtimeout, "Detected that process didn't time out") michael@0: else: michael@0: self.assertTrue(not didtimeout, "Detected that process timed out") michael@0: michael@0: if isalive: michael@0: self.assertTrue(detected, "Detected process is not running, process output: %s" % output) michael@0: else: michael@0: self.assertTrue(not detected, "Detected process is still running, process output: %s" % output) michael@0: michael@0: if __name__ == '__main__': michael@0: unittest.main()