Add support for nosetests multiprocessing plugin.
[sawsim.git] / pysawsim / invoke.py
1 # Copyright (C) 2009-2010  W. Trevor King <wking@drexel.edu>
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 #
16 # The author may be contacted at <wking@drexel.edu> on the Internet, or
17 # write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
18 # Philadelphia PA 19104, USA.
19
20 """Functions for running external commands in subprocesses.
21 """
22
23 from subprocess import Popen, PIPE
24 import sys
25
26
27 _multiprocess_can_split_ = True
28 """Allow nosetests to split tests between processes.
29 """
30
31
32 class CommandError(Exception):
33     """Represent errors in command execution.
34
35     Instances are picklable (for passing through `multiprocessing.Queue`\s).
36
37     >>> import pickle
38     >>> a = CommandError('somefunc', 1, '', 'could not find "somefunc"')
39     >>> x = pickle.dumps(a)
40     >>> b = pickle.loads(x)
41     >>> print b
42     Command failed (1):
43       could not find "somefunc"
44     <BLANKLINE>
45     while executing
46       somefunc
47     >>> print repr(b)  # doctest: +NORMALIZE_WHITESPACE
48     CommandError(command='somefunc', status=1, stdout='',
49                  stderr='could not find "somefunc"')
50     """
51     def __init__(self, command=None, status=None, stdout=None, stderr=None):
52         self.command = command
53         self.status = status
54         self.stdout = stdout
55         self.stderr = stderr
56         Exception.__init__(self, self.__str__())
57
58     def __getstate__(self):
59         return self.__dict__
60
61     def __setstate__(self, data):
62         self.__dict__.update(data)
63
64     def __str__(self):
65         return "\n".join([
66                 "Command failed (%s):\n  %s\n" % (self.status, self.stderr),
67                 "while executing\n  %s" % self.command,
68                 ])
69
70     def __repr__(self):
71         return '%s(%s)' % (
72             self.__class__.__name__,
73             ', '.join(['%s=%s' % (attr, repr(getattr(self, attr)))
74                        for attr in ['command', 'status', 'stdout', 'stderr']]))
75
76
77 def invoke(cmd_string, stdin=None, expect=(0,), cwd=None, verbose=False):
78     """
79     expect should be a tuple of allowed exit codes.  cwd should be
80     the directory from which the command will be executed.
81     """
82     if cwd == None:
83         cwd = "."
84     if verbose == True:
85         print >> sys.stderr, "%s$ %s" % (cwd, cmd_string)
86     try :
87         if sys.platform != "win32" and False:
88             q = Popen(cmd_string, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd)
89         else:
90             # win32 don't have os.execvp() so have to run command in a shell
91             q = Popen(cmd_string, stdin=PIPE, stdout=PIPE, stderr=PIPE,
92                       shell=True, cwd=cwd)
93     except OSError, e :
94         raise CommandError(cmd_string, status=e.args[0], stdout="", stderr=e)
95     stdout,stderr = q.communicate(input=stdin)
96     status = q.wait()
97     if verbose == True:
98         print >> sys.stderr, "%d\n%s%s" % (status, stdout, stderr)
99     if status not in expect:
100         raise CommandError(cmd_string, status, stdout, stderr)
101     return status, stdout, stderr