comparison unix.py @ 82:06356af50bf9

Finished testcases reorganization and CPU time limit implementation We now have: * Win32-specific code in the win32 module (including bug fixes), * UNIX-specific and generic code in the unix module, * a much cleaner testcases module, * wait4-based resource limits working on Python 3 (this is a bug fix), * no warning/error reported on non-Win32 when -x is not passed but standard input does not come from a terminal, * the maxtime configuration variable replaced with two new variables named maxcputime and maxwalltime, * CPU time reported if it can be determined unless an error occurs sooner than it is determined (e. g. if the wall-clock time limit is exceeded), * memory limits enforced even if Upreckon's forking already breaks them, * CPU time limits and private virtual memory limits honoured on Win32, * CPU time limits honoured on UNIX(-like) platforms supporting wait4 or getrusage, * address space limits honoured on UNIX(-like) platforms supporting setrlimit with RLIMIT_AS/RLIMIT_VMEM, * resident set size limits honoured on UNIX(-like) platforms supporting wait4.
author Oleg Oshmyan <chortos@inbox.lv>
date Wed, 23 Feb 2011 23:35:27 +0000
parents
children 741ae3391b61
comparison
equal deleted inserted replaced
81:24752db487c5 82:06356af50bf9
1 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
2
3 from __future__ import division, with_statement
4 import sys
5
6 try:
7 from compat import *
8 import testcases # mutual import
9 except ImportError:
10 import __main__
11 __main__.import_error(sys.exc_info()[1])
12
13 from __main__ import clock
14 from subprocess import Popen
15 import os, sys
16
17 try:
18 from signal import SIGTERM, SIGKILL
19 except ImportError:
20 SIGTERM = 15
21 SIGKILL = 9
22
23 __all__ = 'call', 'kill', 'terminate', 'pause'
24
25
26 if not sys.stdin.isatty():
27 pause = lambda: sys.stdin.read(1)
28 catch_escape = False
29 else:
30 try:
31 from select import select
32 import termios, tty, atexit
33 except ImportError:
34 pause = None
35 catch_escape = False
36 else:
37 catch_escape = True
38 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
39 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
40 atexit.register(cleanup)
41 tty.setcbreak(sys.stdin.fileno())
42 def pause():
43 sys.stdin.read(1)
44
45 try:
46 from signal import SIGCHLD, signal, SIG_DFL
47 from select import select, error as SelectError
48 from errno import EINTR
49 from fcntl import fcntl, F_SETFD, F_GETFD
50 try:
51 import cPickle as pickle
52 except ImportError:
53 import pickle
54 except ImportError:
55 def call(*args, **kwargs):
56 case = kwargs.pop('case')
57 try:
58 case.process = Popen(*args, **kwargs)
59 except OSError:
60 raise CannotStartTestee(sys.exc_info()[1])
61 case.time_started = clock()
62 if not case.maxtime:
63 while True:
64 exitcode, now = case.process.poll(), clock()
65 if exitcode is not None:
66 case.time_stopped = now
67 break
68 else:
69 time.sleep(.001)
70 else:
71 time_end = case.time_started + case.maxtime
72 while True:
73 exitcode, now = case.process.poll(), clock()
74 if exitcode is not None:
75 case.time_stopped = now
76 break
77 elif now >= time_end:
78 raise TimeLimitExceeded
79 else:
80 time.sleep(.001)
81 else:
82 try:
83 from fcntl import FD_CLOEXEC
84 except ImportError:
85 FD_CLOEXEC = 1
86
87 try:
88 from resource import getrusage, RUSAGE_SELF, RUSAGE_CHILDREN
89 except ImportError:
90 from time import clock as cpuclock
91 getrusage = lambda who: None
92 else:
93 def cpuclock():
94 rusage = getrusage(RUSAGE_SELF)
95 return rusage.ru_utime + rusage.ru_stime
96
97 try:
98 from resource import setrlimit
99 try:
100 from resource import RLIMIT_AS
101 except ImportError:
102 from resource import RLIMIT_VMEM
103 except ImportError:
104 setrlimit = None
105
106 # Make SIGCHLD interrupt sleep() and select()
107 def bury_child(signum, frame):
108 try:
109 bury_child.case.time_stopped = clock()
110 except Exception:
111 pass
112 signal(SIGCHLD, bury_child)
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 old_rusage = getrusage(RUSAGE_CHILDREN)
130 last_rusage = None
131 try:
132 case.process = Popen(*args, **kwargs)
133 except OSError:
134 os.close(read)
135 raise testcases.CannotStartTestee(sys.exc_info()[1])
136 finally:
137 os.close(write)
138 try:
139 if not catch_escape:
140 if case.maxwalltime:
141 time.sleep(case.maxwalltime)
142 if case.process.poll() is None:
143 raise testcases.WallTimeLimitExceeded
144 else:
145 case.process.wait()
146 else:
147 if not case.maxwalltime:
148 try:
149 while case.process.poll() is None:
150 if select((sys.stdin,), (), ())[0]:
151 if sys.stdin.read(1) == '\33':
152 raise testcases.CanceledByUser
153 except SelectError:
154 if sys.exc_info()[1].args[0] != EINTR:
155 raise
156 else:
157 case.process.poll()
158 else:
159 time_end = clock() + case.maxwalltime
160 try:
161 while case.process.poll() is None:
162 remaining = time_end - clock()
163 if remaining > 0:
164 if select((sys.stdin,), (), (), remaining)[0]:
165 if sys.stdin.read(1) == '\33':
166 raise testcases.CanceledByUser
167 else:
168 raise testcases.WallTimeLimitExceeded
169 except SelectError:
170 if sys.exc_info()[1].args[0] != EINTR:
171 raise
172 else:
173 case.process.poll()
174 finally:
175 case.time_started, cpustart = pickle.loads(os.read(read, 512))
176 os.close(read)
177 del bury_child.case
178 new_rusage = getrusage(RUSAGE_CHILDREN)
179 if new_rusage and (case.maxcputime or not case.maxwalltime):
180 case.time_started = cpustart
181 case.time_stopped = new_rusage.ru_utime + new_rusage.ru_stime
182 case.time_limit_string = case.cpu_time_limit_string
183 if case.maxcputime and new_rusage:
184 oldtime = old_rusage.ru_utime + old_rusage.ru_stime
185 newtime = new_rusage.ru_utime + new_rusage.ru_stime
186 if newtime - oldtime - cpustart > case.maxcputime:
187 raise testcases.CPUTimeLimitExceeded
188 if case.maxmemory:
189 if sys.platform != 'darwin':
190 maxrss = case.maxmemory * 1024
191 else:
192 maxrss = case.maxmemory * 1048576
193 if last_rusage and last_rusage.ru_maxrss > maxrss:
194 raise testcases.MemoryLimitExceeded
195 elif (new_rusage and
196 new_rusage.ru_maxrss > old_rusage.ru_maxrss and
197 new_rusage.ru_maxrss > maxrss):
198 raise testcases.MemoryLimitExceeded
199
200 # Emulate memory limits on platforms compatible with 4.3BSD but not XSI
201 # I say 'emulate' because the OS will allow excessive memory usage
202 # anyway; Upreckon will just treat the test case as not passed.
203 # To do this, we not only require os.wait4 to be present but also
204 # assume things about the implementation of subprocess.Popen.
205 try:
206 def waitpid_emu(pid, options, _wait4=os.wait4):
207 global last_rusage
208 pid, status, last_rusage = _wait4(pid, options)
209 return pid, status
210 _waitpid = os.waitpid
211 os.waitpid = waitpid_emu
212 try:
213 defaults = Popen._internal_poll.__func__.__defaults__
214 except AttributeError:
215 # Python 2.5
216 defaults = Popen._internal_poll.im_func.func_defaults
217 i = defaults.index(_waitpid)
218 defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:]
219 try:
220 Popen._internal_poll.__func__.__defaults__ = defaults
221 except AttributeError:
222 # Python 2.5 again
223 Popen._internal_poll.im_func.func_defaults = defaults
224 except (AttributeError, ValueError):
225 pass
226
227
228 def kill(process):
229 try:
230 process.kill()
231 except AttributeError:
232 os.kill(process.pid, SIGKILL)
233
234
235 def terminate(process):
236 try:
237 process.terminate()
238 except AttributeError:
239 os.kill(process.pid, SIGTERM)