Mercurial > ~astiob > upreckon > hgweb
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) |