comparison upreckon/unix.py @ 146:d5b6708c1955

Distutils support, reorganization and cleaning up * Removed command-line options -t and -u. * Reorganized code: o all modules are now in package upreckon; o TestCaseNotPassed and its descendants now live in a separate module exceptions; o load_problem now lives in module problem. * Commented out mentions of command-line option -c in --help. * Added a distutils-based setup.py.
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 28 May 2011 14:24:25 +0100
parents unix.py@ed4035661b85
children 65b5c9390010
comparison
equal deleted inserted replaced
145:d2c266c8d820 146:d5b6708c1955
1 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
2
3 from __future__ import division, with_statement
4
5 from .compat import *
6 from .exceptions import *
7
8 from subprocess import Popen
9 import os, sys, time
10
11 if sys.platform.startswith('java'):
12 from time import clock
13 else:
14 from time import time as clock
15
16 try:
17 from signal import SIGTERM, SIGKILL
18 except ImportError:
19 SIGTERM = 15
20 SIGKILL = 9
21
22 __all__ = 'call', 'kill', 'pause', 'clock'
23
24
25 try:
26 from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd
27 from select import select, error as SelectError
28 from errno import EAGAIN, EINTR
29 from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL
30 from os import O_NONBLOCK
31 try:
32 import cPickle as pickle
33 except ImportError:
34 import pickle
35 except ImportError:
36 def call(*args, **kwargs):
37 case = kwargs.pop('case')
38 try:
39 case.process = Popen(*args, **kwargs)
40 except OSError:
41 raise CannotStartTestee(sys.exc_info()[1])
42 case.time_started = clock()
43 if not case.maxwalltime:
44 while True:
45 exitcode, now = case.process.poll(), clock()
46 if exitcode is not None:
47 case.time_stopped = now
48 break
49 else:
50 time.sleep(.001)
51 else:
52 time_end = case.time_started + case.maxwalltime
53 while True:
54 exitcode, now = case.process.poll(), clock()
55 if exitcode is not None:
56 case.time_stopped = now
57 break
58 elif now >= time_end:
59 raise WallTimeLimitExceeded
60 else:
61 time.sleep(.001)
62 else:
63 try:
64 from fcntl import FD_CLOEXEC
65 except ImportError:
66 FD_CLOEXEC = 1
67
68 try:
69 from signal import siginterrupt
70 except ImportError:
71 # Sucks.
72 siginterrupt = lambda signalnum, flag: None
73
74 try:
75 from resource import getrusage, RUSAGE_SELF, RUSAGE_CHILDREN
76 except ImportError:
77 from time import clock as cpuclock
78 getrusage = lambda who: None
79 else:
80 def cpuclock():
81 rusage = getrusage(RUSAGE_SELF)
82 return rusage.ru_utime + rusage.ru_stime
83
84 try:
85 from resource import setrlimit
86 try:
87 from resource import RLIMIT_AS
88 except ImportError:
89 from resource import RLIMIT_VMEM as RLIMIT_AS
90 except ImportError:
91 setrlimit = None
92
93 # Make SIGCHLD interrupt sleep() and select()
94 sigchld_pipe_read, sigchld_pipe_write = os.pipe()
95 fcntl(sigchld_pipe_read, F_SETFL,
96 fcntl(sigchld_pipe_read, F_GETFL) | O_NONBLOCK)
97 fcntl(sigchld_pipe_write, F_SETFL,
98 fcntl(sigchld_pipe_write, F_GETFL) | O_NONBLOCK)
99 def bury_child(signum, frame):
100 try:
101 bury_child.case.time_stopped = clock()
102 except Exception:
103 pass
104 signal(SIGCHLD, bury_child)
105 set_wakeup_fd(sigchld_pipe_write)
106 class SignalIgnorer(object):
107 def __enter__(self):
108 signal(SIGCHLD, SIG_DFL)
109 def __exit__(self, exc_type, exc_value, traceback):
110 signal(SIGCHLD, bury_child)
111 signal_ignorer = SignalIgnorer()
112 __all__ += 'signal_ignorer',
113
114 # If you want this to work portably, don't set any stdio argument to PIPE
115 def call(*args, **kwargs):
116 global last_rusage
117 bury_child.case = case = kwargs.pop('case')
118 read, write = os.pipe()
119 fcntl(write, F_SETFD, fcntl(write, F_GETFD) | FD_CLOEXEC)
120 def preexec_fn():
121 os.close(read)
122 if setrlimit and case.maxmemory:
123 maxmemory = ceil(case.maxmemory * 1048576)
124 setrlimit(RLIMIT_AS, (maxmemory, maxmemory))
125 # I would also set a CPU time limit but I do not want the time
126 # passing between the calls to fork and exec to be counted in
127 os.write(write, pickle.dumps((clock(), cpuclock()), 1))
128 kwargs['preexec_fn'] = preexec_fn
129 # So how the hell do I actually make use of pass_fds?
130 # On 3.1-, calling Popen with pass_fds prints an exception
131 # from Popen.__del__ to stderr. On 3.2, Popen without close_fds
132 # or pass_fds creates a child and fails but that of course
133 # generates a SIGCHLD, which causes problems, and I have
134 # no process ID to wait upon to negate the changes made
135 # by the SIGCHLD handler.
136 kwargs['close_fds'] = False
137 old_rusage = getrusage(RUSAGE_CHILDREN)
138 last_rusage = None
139 while True:
140 try:
141 os.read(sigchld_pipe_read, 512)
142 except OSError:
143 if sys.exc_info()[1].errno == EAGAIN:
144 break
145 else:
146 raise
147 siginterrupt(SIGCHLD, False)
148 try:
149 case.process = Popen(*args, **kwargs)
150 except OSError:
151 os.close(read)
152 raise CannotStartTestee(sys.exc_info()[1])
153 finally:
154 siginterrupt(SIGCHLD, True)
155 os.close(write)
156 try:
157 if not catch_escape:
158 if case.maxwalltime:
159 try:
160 select((sigchld_pipe_read,), (), (), case.maxwalltime)
161 except SelectError:
162 if sys.exc_info()[1].args[0] != EINTR:
163 raise
164 # subprocess in Python 2.6- is not guarded against EINTR
165 try:
166 if case.process.poll() is None:
167 raise WallTimeLimitExceeded
168 except OSError:
169 if sys.exc_info()[1].errno != EINTR:
170 raise
171 else:
172 case.process.poll()
173 else:
174 wait(case.process)
175 else:
176 if not case.maxwalltime:
177 try:
178 while case.process.poll() is None:
179 s = select((sys.stdin, sigchld_pipe_read), (), ())
180 if (s[0] == [sys.stdin] and
181 sys.stdin.read(1) == '\33'):
182 raise CanceledByUser
183 except (SelectError, IOError, OSError):
184 if sys.exc_info()[1].args[0] != EINTR:
185 raise
186 else:
187 case.process.poll()
188 else:
189 time_end = clock() + case.maxwalltime
190 try:
191 while case.process.poll() is None:
192 remaining = time_end - clock()
193 if remaining > 0:
194 s = select((sys.stdin, sigchld_pipe_read),
195 (), (), remaining)
196 if (s[0] == [sys.stdin] and
197 sys.stdin.read(1) == '\33'):
198 raise CanceledByUser
199 else:
200 raise WallTimeLimitExceeded
201 except (SelectError, IOError, OSError):
202 if sys.exc_info()[1].args[0] != EINTR:
203 raise
204 else:
205 case.process.poll()
206 finally:
207 case.time_started, cpustart = pickle.loads(os.read(read, 512))
208 os.close(read)
209 del bury_child.case
210 new_rusage = getrusage(RUSAGE_CHILDREN)
211 if (case.maxwalltime and
212 case.time_stopped - case.time_started > case.maxwalltime):
213 raise WallTimeLimitExceeded
214 if new_rusage:
215 time_started = old_rusage.ru_utime + old_rusage.ru_stime + cpustart
216 time_stopped = new_rusage.ru_utime + new_rusage.ru_stime
217 # Yes, this actually happens
218 if time_started > time_stopped:
219 time_started = time_stopped
220 if case.maxcputime or not case.maxwalltime:
221 case.time_started = time_started
222 case.time_stopped = time_stopped
223 case.time_limit_string = case.cpu_time_limit_string
224 if (case.maxcputime and
225 time_stopped - time_started > case.maxcputime):
226 raise CPUTimeLimitExceeded
227 if case.maxmemory:
228 if sys.platform != 'darwin':
229 maxrss = case.maxmemory * 1024
230 else:
231 maxrss = case.maxmemory * 1048576
232 if last_rusage and last_rusage.ru_maxrss > maxrss:
233 raise MemoryLimitExceeded
234 elif (new_rusage and
235 new_rusage.ru_maxrss > old_rusage.ru_maxrss and
236 new_rusage.ru_maxrss > maxrss):
237 raise MemoryLimitExceeded
238
239 # Emulate memory limits on platforms compatible with 4.3BSD but not XSI
240 # I say 'emulate' because the OS will allow excessive memory usage
241 # anyway; Upreckon will just treat the test case as not passed.
242 # To do this, we not only require os.wait4 to be present but also
243 # assume things about the implementation of subprocess.Popen.
244 try:
245 def waitpid_emu(pid, options, _wait4=os.wait4):
246 global last_rusage
247 pid, status, last_rusage = _wait4(pid, options)
248 return pid, status
249 _waitpid = os.waitpid
250 os.waitpid = waitpid_emu
251 try:
252 defaults = Popen._internal_poll.__func__.__defaults__
253 except AttributeError:
254 # Python 2.5
255 defaults = Popen._internal_poll.im_func.func_defaults
256 i = defaults.index(_waitpid)
257 defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:]
258 try:
259 Popen._internal_poll.__func__.__defaults__ = defaults
260 except AttributeError:
261 # Python 2.5 again
262 Popen._internal_poll.im_func.func_defaults = defaults
263 except (AttributeError, ValueError):
264 pass
265
266
267 def kill(process):
268 try:
269 process.kill()
270 except AttributeError:
271 os.kill(process.pid, SIGKILL)
272 wait(process)
273
274
275 # subprocess in Python 2.6- is not guarded against EINTR
276 try:
277 from errno import EINTR
278 except ImportError:
279 wait = Popen.wait
280 else:
281 def wait(process):
282 while True:
283 try:
284 return process.wait()
285 except OSError:
286 if sys.exc_info()[1].errno != EINTR:
287 raise
288
289
290 try:
291 from ._unix import *
292 except ImportError:
293 if not sys.stdin.isatty():
294 pause = lambda: sys.stdin.read(1)
295 catch_escape = False
296 else:
297 try:
298 from select import select
299 import termios, tty, atexit
300 except ImportError:
301 pause = lambda: sys.stdin.read(1)
302 catch_escape = False
303 else:
304 catch_escape = True
305 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
306 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
307 atexit.register(cleanup)
308 tty.setcbreak(sys.stdin.fileno())
309 def pause():
310 sys.stdin.read(1)