Mercurial > ~astiob > upreckon > hgweb
annotate testcases.py @ 80:809b77302b21
Win32-specific module with memory and CPU time limits
The Win32-specific implementation of call() and friends now lives
in module win32, looks clean and in addition is able to enforce memory
and CPU time limits on NT kernels, in particular on Windows 2000 and up
asking the system to terminate the process as soon as or (in the case
of CPU time) almost as soon as the limits are broken. According to my
observations, malloc() in the limited process does not return NULL
when memory usage is close to the limit and instead crashes the process
(which Upreckon happily translates into 'memory limit exceeded').
The catch is that the module is not actually used yet; coming soon.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Wed, 16 Feb 2011 00:01:33 +0000 |
parents | ee8a99dcaaed |
children | 24752db487c5 |
rev | line source |
---|---|
21 | 1 #! /usr/bin/env python |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
2 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> |
16 | 3 |
43 | 4 # TODO: copy the ansfile if not options.erase even if no validator is used |
5 | |
21 | 6 from __future__ import division, with_statement |
7 | |
8 try: | |
9 from compat import * | |
10 import files, problem, config | |
11 except ImportError: | |
12 import __main__ | |
13 __main__.import_error(sys.exc_info()[1]) | |
14 else: | |
15 from __main__ import clock, options | |
16 | |
17 import glob, re, sys, tempfile, time | |
18 from subprocess import Popen, PIPE, STDOUT | |
19 | |
20 import os | |
21 devnull = open(os.path.devnull, 'w+') | |
22 | |
23 try: | |
24 from signal import SIGTERM, SIGKILL | |
25 except ImportError: | |
26 SIGTERM = 15 | |
27 SIGKILL = 9 | |
28 | |
16 | 29 try: |
21 | 30 from _subprocess import TerminateProcess |
31 except ImportError: | |
32 # CPython 2.5 does define _subprocess.TerminateProcess even though it is | |
33 # not used in the subprocess module, but maybe something else does not | |
34 try: | |
35 import ctypes | |
36 TerminateProcess = ctypes.windll.kernel32.TerminateProcess | |
37 except (ImportError, AttributeError): | |
38 TerminateProcess = None | |
39 | |
22 | 40 |
72
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
41 # Do not show error messages due to errors in the program being tested |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
42 try: |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
43 import ctypes |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
44 try: |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
45 errmode = ctypes.windll.kernel32.GetErrorMode() |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
46 except AttributeError: |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
47 errmode = ctypes.windll.kernel32.SetErrorMode(0) |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
48 errmode |= 0x8003 |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
49 ctypes.windll.kernel32.SetErrorMode(errmode) |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
50 except Exception: |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
51 pass |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
52 |
7520b6bb6636
Windows Error Reporting is now suppressed (at least the dialogs)
Oleg Oshmyan <chortos@inbox.lv>
parents:
71
diff
changeset
|
53 |
22 | 54 # Do the hacky-wacky dark magic needed to catch presses of the Escape button. |
55 # If only Python supported forcible termination of threads... | |
56 if not sys.stdin.isatty(): | |
57 canceled = init_canceled = lambda: False | |
58 pause = None | |
59 else: | |
60 try: | |
61 # Windows has select() too, but it is not the select() we want | |
62 import msvcrt | |
63 except ImportError: | |
64 try: | |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
65 from select import select |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
66 import termios, tty, atexit |
22 | 67 except ImportError: |
68 # It cannot be helped! | |
69 # Silently disable support for killing the program being tested | |
70 canceled = init_canceled = lambda: False | |
71 pause = None | |
72 else: | |
73 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): | |
74 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) | |
75 atexit.register(cleanup) | |
76 del cleanup | |
77 tty.setcbreak(sys.stdin.fileno()) | |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
78 def canceled(select=select, stdin=sys.stdin, read=sys.stdin.read): |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
79 while select((stdin,), (), (), 0)[0]: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
80 if read(1) == '\33': |
22 | 81 return True |
82 return False | |
83 def init_canceled(): | |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
84 while select((sys.stdin,), (), (), 0)[0]: |
22 | 85 sys.stdin.read(1) |
86 def pause(): | |
87 sys.stdin.read(1) | |
88 else: | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
89 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
90 while kbhit(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
91 c = getch() |
22 | 92 if c == '\33': |
93 return True | |
94 elif c == '\0': | |
95 # Let's hope no-one is fiddling with this | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
96 getch() |
22 | 97 return False |
98 def init_canceled(): | |
99 while msvcrt.kbhit(): | |
100 msvcrt.getch() | |
101 def pause(): | |
102 msvcrt.getch() | |
103 | |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
104 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
105 from signal import SIGCHLD, signal, SIG_DFL |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
106 from select import select, error as select_error |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
107 from errno import EINTR |
65
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
108 import fcntl |
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
109 try: |
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
110 import cPickle as pickle |
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
111 except ImportError: |
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
112 import pickle |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
113 except ImportError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
114 try: |
61
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
115 from _subprocess import WAIT_OBJECT_0, STD_INPUT_HANDLE, INFINITE |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
116 except ImportError: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
117 WAIT_OBJECT_0 = 0 |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
118 STD_INPUT_HANDLE = -10 |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
119 INFINITE = -1 |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
120 try: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
121 import ctypes |
61
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
122 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
123 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
124 WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects |
61
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
125 ReadConsoleInputA = ctypes.windll.kernel32.ReadConsoleInputA |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
126 try: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
127 from _subprocess import GetStdHandle |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
128 except ImportError: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
129 GetStdHandle = ctypes.windll.kernel32.GetStdHandle |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
130 except (ImportError, AttributeError): |
61
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
131 console_input = False |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
132 else: |
61
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
133 hStdin = GetStdHandle(STD_INPUT_HANDLE) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
134 console_input = bool(SetConsoleMode(hStdin, 1)) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
135 if console_input: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
136 FlushConsoleInputBuffer(hStdin) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
137 class KEY_EVENT_RECORD(ctypes.Structure): |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
138 _fields_ = (("bKeyDown", ctypes.c_int), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
139 ("wRepeatCount", ctypes.c_ushort), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
140 ("wVirtualKeyCode", ctypes.c_ushort), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
141 ("wVirtualScanCode", ctypes.c_ushort), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
142 ("UnicodeChar", ctypes.c_wchar), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
143 ("dwControlKeyState", ctypes.c_uint)) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
144 class INPUT_RECORD(ctypes.Structure): |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
145 _fields_ = (("EventType", ctypes.c_int), |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
146 ("KeyEvent", KEY_EVENT_RECORD)) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
147 # Memory limits (currently) are not supported |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
148 def call(*args, **kwargs): |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
149 case = kwargs.pop('case') |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
150 try: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
151 case.process = Popen(*args, **kwargs) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
152 except OSError: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
153 raise CannotStartTestee(sys.exc_info()[1]) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
154 case.time_started = clock() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
155 if not console_input: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
156 if case.maxtime: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
157 if WaitForSingleObject(case.process._handle, int(case.maxtime * 1000)) != WAIT_OBJECT_0: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
158 raise TimeLimitExceeded |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
159 else: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
160 case.process.wait() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
161 else: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
162 ir = INPUT_RECORD() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
163 n = ctypes.c_int() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
164 lpHandles = (ctypes.c_int * 2)(hStdin, case.process._handle) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
165 if case.maxtime: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
166 time_end = clock() + case.maxtime |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
167 while case.process.poll() is None: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
168 remaining = time_end - clock() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
169 if remaining > 0: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
170 if WaitForMultipleObjects(2, lpHandles, False, int(remaining * 1000)) == WAIT_OBJECT_0: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
171 ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
172 if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
173 raise CanceledByUser |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
174 else: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
175 raise TimeLimitExceeded |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
176 else: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
177 while case.process.poll() is None: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
178 if WaitForMultipleObjects(2, lpHandles, False, INFINITE) == WAIT_OBJECT_0: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
179 ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
180 if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
181 raise CanceledByUser |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
182 case.time_stopped = clock() |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
183 if not console_input: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
184 try: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
185 try: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
186 from _subprocess import WaitForSingleObject |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
187 except ImportError: |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
188 import ctypes |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
189 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
190 except (ImportError, AttributeError): |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
191 # TODO: move the default implementation here |
24f144e11b5e
Accurate run-time reporting on Win32
Oleg Oshmyan <chortos@inbox.lv>
parents:
58
diff
changeset
|
192 call = None |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
193 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
194 # Make SIGCHLD interrupt sleep() and select() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
195 def bury_child(signum, frame): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
196 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
197 bury_child.case.time_stopped = clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
198 except Exception: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
199 pass |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
200 signal(SIGCHLD, bury_child) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
201 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
202 # If you want this to work, don't set any stdio argument to PIPE |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
203 def call_real(*args, **kwargs): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
204 bury_child.case = case = kwargs.pop('case') |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
205 preexec_fn_ = kwargs.get('preexec_fn', None) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
206 read, write = os.pipe() |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
207 def preexec_fn(): |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
208 os.close(read) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
209 if preexec_fn_: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
210 preexec_fn_() |
65
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
211 fcntl.fcntl(write, fcntl.F_SETFD, fcntl.fcntl(write, fcntl.F_GETFD) | getattr(fcntl, 'FD_CLOEXEC', 1)) |
69
c0f1b87013ad
Fixed a crash on Python 3 on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
65
diff
changeset
|
212 fwrite = os.fdopen(write, 'wb') |
65
fcb5ab97f08e
Improved run-time reporting and fixed a potential hang on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
63
diff
changeset
|
213 pickle.dump(clock(), fwrite, 1) |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
214 kwargs['preexec_fn'] = preexec_fn |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
215 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
216 case.process = Popen(*args, **kwargs) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
217 except OSError: |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
218 os.close(read) |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
219 raise CannotStartTestee(sys.exc_info()[1]) |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
220 finally: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
221 os.close(write) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
222 try: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
223 if pause is None: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
224 if case.maxtime: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
225 time.sleep(case.maxtime) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
226 if case.process.poll() is None: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
227 raise TimeLimitExceeded |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
228 else: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
229 case.process.wait() |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
230 else: |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
231 if not case.maxtime: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
232 try: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
233 while case.process.poll() is None: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
234 if select((sys.stdin,), (), ())[0]: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
235 if sys.stdin.read(1) == '\33': |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
236 raise CanceledByUser |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
237 except select_error: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
238 if sys.exc_info()[1].args[0] != EINTR: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
239 raise |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
240 else: |
63
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
241 case.process.poll() |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
242 else: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
243 time_end = clock() + case.maxtime |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
244 try: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
245 while case.process.poll() is None: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
246 remaining = time_end - clock() |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
247 if remaining > 0: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
248 if select((sys.stdin,), (), (), remaining)[0]: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
249 if sys.stdin.read(1) == '\33': |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
250 raise CanceledByUser |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
251 else: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
252 raise TimeLimitExceeded |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
253 except select_error: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
254 if sys.exc_info()[1].args[0] != EINTR: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
255 raise |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
256 else: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
257 case.process.poll() |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
258 finally: |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
259 case.time_started = pickle.loads(os.read(read, 512)) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
260 os.close(read) |
fb9d0223a871
Fixed negative run times reported on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
62
diff
changeset
|
261 del bury_child.case |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
262 def call(*args, **kwargs): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
263 if 'preexec_fn' in kwargs: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
264 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
265 return call_real(*args, **kwargs) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
266 except MemoryError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
267 # If there is not enough memory for the forked test.py, |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
268 # opt for silent dropping of the limit |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
269 # TODO: show a warning somewhere |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
270 del kwargs['preexec_fn'] |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
271 return call_real(*args, **kwargs) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
272 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
273 return call_real(*args, **kwargs) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
274 |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
275 # Emulate memory limits on platforms compatible with 4.3BSD but not XSI |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
276 # I say 'emulate' because the OS will allow excessive memory usage |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
277 # anyway; Upreckon will just treat the test case as not passed. |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
278 # To do this, we not only require os.wait4 to be present but also |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
279 # assume things about the implementation of subprocess.Popen. |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
280 try: |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
281 def waitpid_emu(pid, options, _wait4=os.wait4): |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
282 global last_rusage |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
283 pid, status, last_rusage = _wait4(pid, options) |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
284 return pid, status |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
285 _waitpid = os.waitpid |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
286 os.waitpid = waitpid_emu |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
287 try: |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
288 defaults = Popen._internal_poll.__func__.__defaults__ |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
289 except AttributeError: |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
290 # Python 2.5 |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
291 defaults = Popen._internal_poll.im_func.func_defaults |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
292 i = defaults.index(_waitpid) |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
293 defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:] |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
294 try: |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
295 Popen._internal_poll.__func__.__defaults__ = defaults |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
296 except AttributeError: |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
297 pass |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
298 Popen._internal_poll.im_func.func_defaults = defaults |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
299 except (AttributeError, ValueError): |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
300 pass |
22 | 301 |
21 | 302 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', |
22 | 303 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', |
304 'NonZeroExitCode', 'CannotStartTestee', | |
305 'CannotStartValidator', 'CannotReadOutputFile', | |
306 'CannotReadInputFile', 'CannotReadAnswerFile') | |
21 | 307 |
308 | |
309 | |
310 # Exceptions | |
311 | |
312 class TestCaseNotPassed(Exception): __slots__ = () | |
313 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () | |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
314 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = () |
22 | 315 class CanceledByUser(TestCaseNotPassed): __slots__ = () |
21 | 316 |
317 class WrongAnswer(TestCaseNotPassed): | |
318 __slots__ = 'comment' | |
319 def __init__(self, comment=''): | |
320 self.comment = comment | |
321 | |
322 class NonZeroExitCode(TestCaseNotPassed): | |
323 __slots__ = 'exitcode' | |
324 def __init__(self, exitcode): | |
325 self.exitcode = exitcode | |
326 | |
327 class ExceptionWrapper(TestCaseNotPassed): | |
328 __slots__ = 'upstream' | |
329 def __init__(self, upstream): | |
330 self.upstream = upstream | |
331 | |
332 class CannotStartTestee(ExceptionWrapper): __slots__ = () | |
333 class CannotStartValidator(ExceptionWrapper): __slots__ = () | |
334 class CannotReadOutputFile(ExceptionWrapper): __slots__ = () | |
335 class CannotReadInputFile(ExceptionWrapper): __slots__ = () | |
336 class CannotReadAnswerFile(ExceptionWrapper): __slots__ = () | |
337 | |
338 | |
339 | |
22 | 340 # Helper context managers |
341 | |
342 class CopyDeleting(object): | |
343 __slots__ = 'case', 'file', 'name' | |
344 | |
345 def __init__(self, case, file, name): | |
346 self.case = case | |
347 self.file = file | |
348 self.name = name | |
349 | |
350 def __enter__(self): | |
351 if self.name: | |
352 try: | |
353 self.file.copy(self.name) | |
354 except: | |
355 try: | |
356 self.__exit__(None, None, None) | |
357 except: | |
358 pass | |
359 raise | |
360 | |
361 def __exit__(self, exc_type, exc_val, exc_tb): | |
362 if self.name: | |
363 self.case.files_to_delete.append(self.name) | |
364 | |
365 | |
366 class Copying(object): | |
367 __slots__ = 'file', 'name' | |
368 | |
369 def __init__(self, file, name): | |
370 self.file = file | |
371 self.name = name | |
372 | |
373 def __enter__(self): | |
374 if self.name: | |
375 self.file.copy(self.name) | |
376 | |
377 def __exit__(self, exc_type, exc_val, exc_tb): | |
378 pass | |
379 | |
380 | |
381 | |
21 | 382 # Test case types |
16 | 383 |
384 class TestCase(object): | |
21 | 385 __slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points', |
386 'process', 'time_started', 'time_stopped', 'time_limit_string', | |
22 | 387 'realinname', 'realoutname', 'maxtime', 'maxmemory', |
388 'has_called_back', 'files_to_delete') | |
21 | 389 |
390 if ABCMeta: | |
391 __metaclass__ = ABCMeta | |
16 | 392 |
21 | 393 def __init__(case, prob, id, isdummy, points): |
16 | 394 case.problem = prob |
21 | 395 case.id = id |
396 case.isdummy = isdummy | |
397 case.points = points | |
398 case.maxtime = case.problem.config.maxtime | |
399 case.maxmemory = case.problem.config.maxmemory | |
400 if case.maxtime: | |
401 case.time_limit_string = '/%.3f' % case.maxtime | |
402 else: | |
403 case.time_limit_string = '' | |
404 if not isdummy: | |
405 case.realinname = case.problem.config.testcaseinname | |
406 case.realoutname = case.problem.config.testcaseoutname | |
407 else: | |
408 case.realinname = case.problem.config.dummyinname | |
409 case.realoutname = case.problem.config.dummyoutname | |
410 | |
411 @abstractmethod | |
412 def test(case): raise NotImplementedError | |
16 | 413 |
22 | 414 def __call__(case, callback): |
415 case.has_called_back = False | |
416 case.files_to_delete = [] | |
21 | 417 try: |
22 | 418 return case.test(callback) |
21 | 419 finally: |
22 | 420 now = clock() |
421 if not getattr(case, 'time_started', None): | |
422 case.time_started = case.time_stopped = now | |
423 elif not getattr(case, 'time_stopped', None): | |
424 case.time_stopped = now | |
425 if not case.has_called_back: | |
426 callback() | |
21 | 427 case.cleanup() |
428 | |
429 def cleanup(case): | |
430 #if getattr(case, 'infile', None): | |
431 # case.infile.close() | |
432 #if getattr(case, 'outfile', None): | |
433 # case.outfile.close() | |
434 if getattr(case, 'process', None): | |
435 # Try killing after three unsuccessful TERM attempts in a row | |
436 # (except on Windows, where TERMing is killing) | |
437 for i in range(3): | |
438 try: | |
439 try: | |
440 case.process.terminate() | |
441 except AttributeError: | |
442 # Python 2.5 | |
443 if TerminateProcess and hasattr(proc, '_handle'): | |
444 # Windows API | |
445 TerminateProcess(proc._handle, 1) | |
446 else: | |
447 # POSIX | |
448 os.kill(proc.pid, SIGTERM) | |
449 except Exception: | |
450 time.sleep(0) | |
451 case.process.poll() | |
452 else: | |
22 | 453 case.process.wait() |
21 | 454 break |
455 else: | |
456 # If killing the process is unsuccessful three times in a row, | |
457 # just silently stop trying | |
458 for i in range(3): | |
459 try: | |
460 try: | |
461 case.process.kill() | |
462 except AttributeError: | |
463 # Python 2.5 | |
464 if TerminateProcess and hasattr(proc, '_handle'): | |
465 # Windows API | |
466 TerminateProcess(proc._handle, 1) | |
467 else: | |
468 # POSIX | |
469 os.kill(proc.pid, SIGKILL) | |
470 except Exception: | |
471 time.sleep(0) | |
472 case.process.poll() | |
473 else: | |
22 | 474 case.process.wait() |
21 | 475 break |
22 | 476 if case.files_to_delete: |
477 for name in case.files_to_delete: | |
478 try: | |
479 os.remove(name) | |
480 except Exception: | |
481 # It can't be helped | |
482 pass | |
21 | 483 |
484 def open_infile(case): | |
485 try: | |
486 case.infile = files.File('/'.join((case.problem.name, case.realinname.replace('$', case.id)))) | |
487 except IOError: | |
488 e = sys.exc_info()[1] | |
489 raise CannotReadInputFile(e) | |
490 | |
491 def open_outfile(case): | |
492 try: | |
493 case.outfile = files.File('/'.join((case.problem.name, case.realoutname.replace('$', case.id)))) | |
494 except IOError: | |
495 e = sys.exc_info()[1] | |
496 raise CannotReadAnswerFile(e) | |
497 | |
16 | 498 |
21 | 499 class ValidatedTestCase(TestCase): |
500 __slots__ = 'validator' | |
501 | |
502 def __init__(case, *args): | |
503 TestCase.__init__(case, *args) | |
504 if not case.problem.config.tester: | |
505 case.validator = None | |
506 else: | |
507 case.validator = case.problem.config.tester | |
508 | |
509 def validate(case, output): | |
510 if not case.validator: | |
511 # Compare the output with the reference output | |
512 case.open_outfile() | |
513 with case.outfile.open() as refoutput: | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
514 for line, refline in zip_longest(output, refoutput): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
515 if refline is not None and not isinstance(refline, basestring): |
21 | 516 line = bytes(line, sys.getdefaultencoding()) |
517 if line != refline: | |
22 | 518 raise WrongAnswer |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
519 return 1 |
21 | 520 elif callable(case.validator): |
521 return case.validator(output) | |
522 else: | |
523 # Call the validator program | |
524 output.close() | |
23 | 525 if case.problem.config.ansname: |
526 case.open_outfile() | |
527 case.outfile.copy(case.problem.config.ansname) | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
528 try: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
529 case.process = Popen(case.validator, stdin=devnull, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=-1) |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
530 except OSError: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
531 raise CannotStartValidator(sys.exc_info()[1]) |
21 | 532 comment = case.process.communicate()[0].strip() |
26 | 533 match = re.match(r'(?i)(ok|(?:correct|wrong)(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', comment) |
21 | 534 if match: |
535 comment = comment[match.end():] | |
536 if not case.problem.config.maxexitcode: | |
537 if case.process.returncode: | |
538 raise WrongAnswer(comment) | |
539 else: | |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
540 return 1, comment |
21 | 541 else: |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
542 return case.process.returncode / case.problem.config.maxexitcode, comment |
21 | 543 |
544 | |
545 class BatchTestCase(ValidatedTestCase): | |
546 __slots__ = () | |
547 | |
22 | 548 def test(case, callback): |
549 init_canceled() | |
21 | 550 if sys.platform == 'win32' or not case.maxmemory: |
551 preexec_fn = None | |
552 else: | |
553 def preexec_fn(): | |
554 try: | |
555 import resource | |
556 maxmemory = int(case.maxmemory * 1048576) | |
557 resource.setrlimit(resource.RLIMIT_AS, (maxmemory, maxmemory)) | |
558 # I would also set a CPU time limit but I do not want the time | |
559 # that passes between the calls to fork and exec to be counted in | |
560 except MemoryError: | |
561 # We do not have enough memory for ourselves; | |
562 # let the parent know about this | |
563 raise | |
564 except Exception: | |
565 # Well, at least we tried | |
566 pass | |
567 case.open_infile() | |
568 case.time_started = None | |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
569 global last_rusage |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
570 last_rusage = None |
21 | 571 if case.problem.config.stdio: |
54 | 572 if options.erase and not case.validator or not case.problem.config.inname: |
22 | 573 # TODO: re-use the same file name if possible |
21 | 574 # FIXME: 2.5 lacks the delete parameter |
575 with tempfile.NamedTemporaryFile(delete=False) as f: | |
22 | 576 inputdatafname = f.name |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
577 contextmgr = CopyDeleting(case, case.infile, inputdatafname) |
21 | 578 else: |
579 inputdatafname = case.problem.config.inname | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
580 contextmgr = Copying(case.infile, inputdatafname) |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
581 with contextmgr: |
79
ee8a99dcaaed
Renamed configuration variable tasknames to problems
Oleg Oshmyan <chortos@inbox.lv>
parents:
77
diff
changeset
|
582 with open(inputdatafname) as infile: |
22 | 583 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: |
57
855bdfeb32a6
NameErrors within call() are now reported
Oleg Oshmyan <chortos@inbox.lv>
parents:
56
diff
changeset
|
584 if call is not None: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
585 call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) |
57
855bdfeb32a6
NameErrors within call() are now reported
Oleg Oshmyan <chortos@inbox.lv>
parents:
56
diff
changeset
|
586 else: |
22 | 587 try: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
588 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
589 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
590 except MemoryError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
591 # If there is not enough memory for the forked test.py, |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
592 # opt for silent dropping of the limit |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
593 # TODO: show a warning somewhere |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
594 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
595 except OSError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
596 raise CannotStartTestee(sys.exc_info()[1]) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
597 case.time_started = clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
598 time_next_check = case.time_started + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
599 if not case.maxtime: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
600 while True: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
601 exitcode, now = case.process.poll(), clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
602 if exitcode is not None: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
603 case.time_stopped = now |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
604 break |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
605 # For some reason (probably Microsoft's fault), |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
606 # msvcrt.kbhit() is slow as hell |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
607 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
608 if now >= time_next_check: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
609 if canceled(): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
610 raise CanceledByUser |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
611 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
612 time_next_check = now + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
613 time.sleep(.001) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
614 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
615 time_end = case.time_started + case.maxtime |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
616 while True: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
617 exitcode, now = case.process.poll(), clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
618 if exitcode is not None: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
619 case.time_stopped = now |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
620 break |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
621 elif now >= time_end: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
622 raise TimeLimitExceeded |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
623 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
624 if now >= time_next_check: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
625 if canceled(): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
626 raise CanceledByUser |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
627 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
628 time_next_check = now + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
629 time.sleep(.001) |
62
593ad09cd69b
Multiple exit code handling fixes
Oleg Oshmyan <chortos@inbox.lv>
parents:
61
diff
changeset
|
630 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
22 | 631 raise NonZeroExitCode(case.process.returncode) |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
632 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
633 raise MemoryLimitExceeded |
22 | 634 callback() |
635 case.has_called_back = True | |
636 outfile.seek(0) | |
637 return case.validate(outfile) | |
21 | 638 else: |
22 | 639 case.infile.copy(case.problem.config.inname) |
57
855bdfeb32a6
NameErrors within call() are now reported
Oleg Oshmyan <chortos@inbox.lv>
parents:
56
diff
changeset
|
640 if call is not None: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
641 call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) |
57
855bdfeb32a6
NameErrors within call() are now reported
Oleg Oshmyan <chortos@inbox.lv>
parents:
56
diff
changeset
|
642 else: |
21 | 643 try: |
56
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
644 try: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
645 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
646 except MemoryError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
647 # If there is not enough memory for the forked test.py, |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
648 # opt for silent dropping of the limit |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
649 # TODO: show a warning somewhere |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
650 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
651 except OSError: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
652 raise CannotStartTestee(sys.exc_info()[1]) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
653 case.time_started = clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
654 time_next_check = case.time_started + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
655 if not case.maxtime: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
656 while True: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
657 exitcode, now = case.process.poll(), clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
658 if exitcode is not None: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
659 case.time_stopped = now |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
660 break |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
661 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
662 if now >= time_next_check: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
663 if canceled(): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
664 raise CanceledByUser |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
665 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
666 time_next_check = now + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
667 time.sleep(.001) |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
668 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
669 time_end = case.time_started + case.maxtime |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
670 while True: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
671 exitcode, now = case.process.poll(), clock() |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
672 if exitcode is not None: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
673 case.time_stopped = now |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
674 break |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
675 elif now >= time_end: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
676 raise TimeLimitExceeded |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
677 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
678 if now >= time_next_check: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
679 if canceled(): |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
680 raise CanceledByUser |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
681 else: |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
682 time_next_check = now + .15 |
693a938bdeee
Accurate run-time reporting on POSIX
Oleg Oshmyan <chortos@inbox.lv>
parents:
55
diff
changeset
|
683 time.sleep(.001) |
62
593ad09cd69b
Multiple exit code handling fixes
Oleg Oshmyan <chortos@inbox.lv>
parents:
61
diff
changeset
|
684 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
21 | 685 raise NonZeroExitCode(case.process.returncode) |
77
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
686 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): |
69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
Oleg Oshmyan <chortos@inbox.lv>
parents:
76
diff
changeset
|
687 raise MemoryLimitExceeded |
22 | 688 callback() |
689 case.has_called_back = True | |
21 | 690 with open(case.problem.config.outname, 'rU') as output: |
691 return case.validate(output) | |
692 | |
693 | |
694 # This is the only test case type not executing any programs to be tested | |
695 class OutputOnlyTestCase(ValidatedTestCase): | |
696 __slots__ = () | |
697 def cleanup(case): pass | |
698 | |
699 class BestOutputTestCase(ValidatedTestCase): | |
700 __slots__ = () | |
701 | |
702 # This is the only test case type executing two programs simultaneously | |
703 class ReactiveTestCase(TestCase): | |
704 __slots__ = () | |
705 # The basic idea is to launch the program to be tested and the grader | |
706 # and to pipe their standard I/O from and to each other, | |
707 # and then to capture the grader's exit code and use it | |
26 | 708 # like the exit code of an output validator is used. |
21 | 709 |
710 | |
71
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
711 class DummyTestContext(problem.TestGroup): |
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
712 __slots__ = () |
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
713 def end(self): |
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
714 say('Sample total: %d/%d tests' % (self.ncorrect, self.ntotal)) |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
715 return 0, 0, self.log |
71
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
716 |
21 | 717 def load_problem(prob, _types={'batch' : BatchTestCase, |
718 'outonly' : OutputOnlyTestCase, | |
719 'bestout' : BestOutputTestCase, | |
720 'reactive': ReactiveTestCase}): | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
721 # We will need to iterate over these configuration variables twice |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
722 try: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
723 len(prob.config.dummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
724 except Exception: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
725 prob.config.dummies = tuple(prob.config.dummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
726 try: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
727 len(prob.config.tests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
728 except Exception: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
729 prob.config.tests = tuple(prob.config.tests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
730 |
23 | 731 if options.legacy: |
732 prob.config.usegroups = False | |
58 | 733 newtests = [] |
23 | 734 for i, name in enumerate(prob.config.tests): |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
735 # Same here; we'll need to iterate over them twice |
23 | 736 try: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
737 l = len(name) |
23 | 738 except Exception: |
739 try: | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
740 name = tuple(name) |
23 | 741 except TypeError: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
742 name = (name,) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
743 l = len(name) |
58 | 744 if l > 1: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
745 prob.config.usegroups = True |
58 | 746 newtests.append(name) |
747 if prob.config.usegroups: | |
748 prob.config.tests = newtests | |
749 del newtests | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
750 |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
751 # Even if they have duplicate test identifiers, we must honour sequence pointmaps |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
752 if isinstance(prob.config.pointmap, dict): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
753 def getpoints(i, j, k=None): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
754 try: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
755 return prob.config.pointmap[i] |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
756 except KeyError: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
757 try: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
758 return prob.config.pointmap[None] |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
759 except KeyError: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
760 return prob.config.maxexitcode or 1 |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
761 elif prob.config.usegroups: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
762 def getpoints(i, j, k): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
763 try: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
764 return prob.config.pointmap[k][j] |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
765 except LookupError: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
766 return prob.config.maxexitcode or 1 |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
767 else: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
768 def getpoints(i, j): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
769 try: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
770 return prob.config.pointmap[j] |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
771 except LookupError: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
772 return prob.config.maxexitcode or 1 |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
773 |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
774 # First get prob.cache.padoutput right, |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
775 # then yield the actual test cases |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
776 for i in prob.config.dummies: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
777 s = 'sample ' + str(i).zfill(prob.config.paddummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
778 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) |
16 | 779 if prob.config.usegroups: |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
780 if not isinstance(prob.config.groupweight, dict): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
781 prob.config.groupweight = dict(enumerate(prob.config.groupweight)) |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
782 for group in prob.config.tests: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
783 for i in group: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
784 s = str(i).zfill(prob.config.padtests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
785 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) |
71
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
786 yield DummyTestContext() |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
787 for i in prob.config.dummies: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
788 s = str(i).zfill(prob.config.paddummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
789 yield _types[prob.config.kind](prob, s, True, 0) |
71
1bee3a0beeb5
Added a 'Sample total' line when using test groups
Oleg Oshmyan <chortos@inbox.lv>
parents:
69
diff
changeset
|
790 yield problem.test_context_end |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
791 for k, group in enumerate(prob.config.tests): |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
792 if not group: |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
793 continue |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
794 yield problem.TestGroup(prob.config.groupweight.get(k, prob.config.groupweight.get(None))) |
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
795 for j, i in enumerate(group): |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
796 s = str(i).zfill(prob.config.padtests) |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
797 yield _types[prob.config.kind](prob, s, False, getpoints(i, j, k)) |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
798 yield problem.test_context_end |
16 | 799 else: |
800 for i in prob.config.tests: | |
21 | 801 s = str(i).zfill(prob.config.padtests) |
802 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) | |
803 for i in prob.config.dummies: | |
804 s = str(i).zfill(prob.config.paddummies) | |
805 yield _types[prob.config.kind](prob, s, True, 0) | |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
806 for j, i in enumerate(prob.config.tests): |
21 | 807 s = str(i).zfill(prob.config.padtests) |
76
0e5ae28e0b2b
Points are now weighted on a test context basis
Oleg Oshmyan <chortos@inbox.lv>
parents:
72
diff
changeset
|
808 yield _types[prob.config.kind](prob, s, False, getpoints(i, j)) |