Mercurial > ~astiob > upreckon > hgweb
annotate 2.00/testcases.py @ 40:af9c45708987
Cemented a decision previously being unsure about
The mere presense of the tasknames configuration variable now always makes problem names to be printed.
This is not new, but the old behaviour (only printing names if we test more than one problem), previously commented out, has now been removed altogether.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Sun, 05 Dec 2010 14:34:24 +0100 |
parents | 2b459f9743b4 |
children | 164395af969d |
rev | line source |
---|---|
21 | 1 #! /usr/bin/env python |
16 | 2 # Copyright (c) 2010 Chortos-2 <chortos@inbox.lv> |
3 | |
21 | 4 from __future__ import division, with_statement |
5 | |
6 try: | |
7 from compat import * | |
8 import files, problem, config | |
9 except ImportError: | |
10 import __main__ | |
11 __main__.import_error(sys.exc_info()[1]) | |
12 else: | |
13 from __main__ import clock, options | |
14 | |
15 import glob, re, sys, tempfile, time | |
16 from subprocess import Popen, PIPE, STDOUT | |
17 | |
18 import os | |
19 devnull = open(os.path.devnull, 'w+') | |
20 | |
21 try: | |
22 from signal import SIGTERM, SIGKILL | |
23 except ImportError: | |
24 SIGTERM = 15 | |
25 SIGKILL = 9 | |
26 | |
16 | 27 try: |
21 | 28 from _subprocess import TerminateProcess |
29 except ImportError: | |
30 # CPython 2.5 does define _subprocess.TerminateProcess even though it is | |
31 # not used in the subprocess module, but maybe something else does not | |
32 try: | |
33 import ctypes | |
34 TerminateProcess = ctypes.windll.kernel32.TerminateProcess | |
35 except (ImportError, AttributeError): | |
36 TerminateProcess = None | |
37 | |
22 | 38 |
39 # Do the hacky-wacky dark magic needed to catch presses of the Escape button. | |
40 # If only Python supported forcible termination of threads... | |
41 if not sys.stdin.isatty(): | |
42 canceled = init_canceled = lambda: False | |
43 pause = None | |
44 else: | |
45 try: | |
46 # Windows has select() too, but it is not the select() we want | |
47 import msvcrt | |
48 except ImportError: | |
49 try: | |
50 import select, termios, tty, atexit | |
51 except ImportError: | |
52 # It cannot be helped! | |
53 # Silently disable support for killing the program being tested | |
54 canceled = init_canceled = lambda: False | |
55 pause = None | |
56 else: | |
57 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): | |
58 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) | |
59 atexit.register(cleanup) | |
60 del cleanup | |
61 tty.setcbreak(sys.stdin.fileno()) | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
62 def canceled(select=select.select, stdin=sys.stdin, read=sys.stdin.read): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
63 while select((stdin,), (), (), 0)[0]: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
64 if read(1) == '\33': |
22 | 65 return True |
66 return False | |
67 def init_canceled(): | |
68 while select.select((sys.stdin,), (), (), 0)[0]: | |
69 sys.stdin.read(1) | |
70 def pause(): | |
71 sys.stdin.read(1) | |
72 else: | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
73 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
74 while kbhit(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
75 c = getch() |
22 | 76 if c == '\33': |
77 return True | |
78 elif c == '\0': | |
79 # 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
|
80 getch() |
22 | 81 return False |
82 def init_canceled(): | |
83 while msvcrt.kbhit(): | |
84 msvcrt.getch() | |
85 def pause(): | |
86 msvcrt.getch() | |
87 | |
88 | |
21 | 89 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', |
22 | 90 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', |
91 'NonZeroExitCode', 'CannotStartTestee', | |
92 'CannotStartValidator', 'CannotReadOutputFile', | |
93 'CannotReadInputFile', 'CannotReadAnswerFile') | |
21 | 94 |
95 | |
96 | |
97 # Exceptions | |
98 | |
99 class TestCaseNotPassed(Exception): __slots__ = () | |
100 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () | |
22 | 101 class CanceledByUser(TestCaseNotPassed): __slots__ = () |
21 | 102 |
103 class WrongAnswer(TestCaseNotPassed): | |
104 __slots__ = 'comment' | |
105 def __init__(self, comment=''): | |
106 self.comment = comment | |
107 | |
108 class NonZeroExitCode(TestCaseNotPassed): | |
109 __slots__ = 'exitcode' | |
110 def __init__(self, exitcode): | |
111 self.exitcode = exitcode | |
112 | |
113 class ExceptionWrapper(TestCaseNotPassed): | |
114 __slots__ = 'upstream' | |
115 def __init__(self, upstream): | |
116 self.upstream = upstream | |
117 | |
118 class CannotStartTestee(ExceptionWrapper): __slots__ = () | |
119 class CannotStartValidator(ExceptionWrapper): __slots__ = () | |
120 class CannotReadOutputFile(ExceptionWrapper): __slots__ = () | |
121 class CannotReadInputFile(ExceptionWrapper): __slots__ = () | |
122 class CannotReadAnswerFile(ExceptionWrapper): __slots__ = () | |
123 | |
124 | |
125 | |
22 | 126 # Helper context managers |
127 | |
128 class CopyDeleting(object): | |
129 __slots__ = 'case', 'file', 'name' | |
130 | |
131 def __init__(self, case, file, name): | |
132 self.case = case | |
133 self.file = file | |
134 self.name = name | |
135 | |
136 def __enter__(self): | |
137 if self.name: | |
138 try: | |
139 self.file.copy(self.name) | |
140 except: | |
141 try: | |
142 self.__exit__(None, None, None) | |
143 except: | |
144 pass | |
145 raise | |
146 | |
147 def __exit__(self, exc_type, exc_val, exc_tb): | |
148 if self.name: | |
149 self.case.files_to_delete.append(self.name) | |
150 | |
151 | |
152 class Copying(object): | |
153 __slots__ = 'file', 'name' | |
154 | |
155 def __init__(self, file, name): | |
156 self.file = file | |
157 self.name = name | |
158 | |
159 def __enter__(self): | |
160 if self.name: | |
161 self.file.copy(self.name) | |
162 | |
163 def __exit__(self, exc_type, exc_val, exc_tb): | |
164 pass | |
165 | |
166 | |
167 | |
21 | 168 # Test case types |
16 | 169 |
170 class TestCase(object): | |
21 | 171 __slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points', |
172 'process', 'time_started', 'time_stopped', 'time_limit_string', | |
22 | 173 'realinname', 'realoutname', 'maxtime', 'maxmemory', |
174 'has_called_back', 'files_to_delete') | |
21 | 175 |
176 if ABCMeta: | |
177 __metaclass__ = ABCMeta | |
16 | 178 |
21 | 179 def __init__(case, prob, id, isdummy, points): |
16 | 180 case.problem = prob |
21 | 181 case.id = id |
182 case.isdummy = isdummy | |
183 case.points = points | |
184 case.maxtime = case.problem.config.maxtime | |
185 case.maxmemory = case.problem.config.maxmemory | |
186 if case.maxtime: | |
187 case.time_limit_string = '/%.3f' % case.maxtime | |
188 else: | |
189 case.time_limit_string = '' | |
190 if not isdummy: | |
191 case.realinname = case.problem.config.testcaseinname | |
192 case.realoutname = case.problem.config.testcaseoutname | |
193 else: | |
194 case.realinname = case.problem.config.dummyinname | |
195 case.realoutname = case.problem.config.dummyoutname | |
196 | |
197 @abstractmethod | |
198 def test(case): raise NotImplementedError | |
16 | 199 |
22 | 200 def __call__(case, callback): |
201 case.has_called_back = False | |
202 case.files_to_delete = [] | |
21 | 203 try: |
22 | 204 return case.test(callback) |
21 | 205 finally: |
22 | 206 now = clock() |
207 if not getattr(case, 'time_started', None): | |
208 case.time_started = case.time_stopped = now | |
209 elif not getattr(case, 'time_stopped', None): | |
210 case.time_stopped = now | |
211 if not case.has_called_back: | |
212 callback() | |
21 | 213 case.cleanup() |
214 | |
215 def cleanup(case): | |
216 #if getattr(case, 'infile', None): | |
217 # case.infile.close() | |
218 #if getattr(case, 'outfile', None): | |
219 # case.outfile.close() | |
220 if getattr(case, 'process', None): | |
221 # Try killing after three unsuccessful TERM attempts in a row | |
222 # (except on Windows, where TERMing is killing) | |
223 for i in range(3): | |
224 try: | |
225 try: | |
226 case.process.terminate() | |
227 except AttributeError: | |
228 # Python 2.5 | |
229 if TerminateProcess and hasattr(proc, '_handle'): | |
230 # Windows API | |
231 TerminateProcess(proc._handle, 1) | |
232 else: | |
233 # POSIX | |
234 os.kill(proc.pid, SIGTERM) | |
235 except Exception: | |
236 time.sleep(0) | |
237 case.process.poll() | |
238 else: | |
22 | 239 case.process.wait() |
21 | 240 break |
241 else: | |
242 # If killing the process is unsuccessful three times in a row, | |
243 # just silently stop trying | |
244 for i in range(3): | |
245 try: | |
246 try: | |
247 case.process.kill() | |
248 except AttributeError: | |
249 # Python 2.5 | |
250 if TerminateProcess and hasattr(proc, '_handle'): | |
251 # Windows API | |
252 TerminateProcess(proc._handle, 1) | |
253 else: | |
254 # POSIX | |
255 os.kill(proc.pid, SIGKILL) | |
256 except Exception: | |
257 time.sleep(0) | |
258 case.process.poll() | |
259 else: | |
22 | 260 case.process.wait() |
21 | 261 break |
22 | 262 if case.files_to_delete: |
263 for name in case.files_to_delete: | |
264 try: | |
265 os.remove(name) | |
266 except Exception: | |
267 # It can't be helped | |
268 pass | |
21 | 269 |
270 def open_infile(case): | |
271 try: | |
272 case.infile = files.File('/'.join((case.problem.name, case.realinname.replace('$', case.id)))) | |
273 except IOError: | |
274 e = sys.exc_info()[1] | |
275 raise CannotReadInputFile(e) | |
276 | |
277 def open_outfile(case): | |
278 try: | |
279 case.outfile = files.File('/'.join((case.problem.name, case.realoutname.replace('$', case.id)))) | |
280 except IOError: | |
281 e = sys.exc_info()[1] | |
282 raise CannotReadAnswerFile(e) | |
283 | |
16 | 284 |
21 | 285 class ValidatedTestCase(TestCase): |
286 __slots__ = 'validator' | |
287 | |
288 def __init__(case, *args): | |
289 TestCase.__init__(case, *args) | |
290 if not case.problem.config.tester: | |
291 case.validator = None | |
292 else: | |
293 case.validator = case.problem.config.tester | |
294 | |
295 def validate(case, output): | |
296 if not case.validator: | |
297 # Compare the output with the reference output | |
298 case.open_outfile() | |
299 with case.outfile.open() as refoutput: | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
300 for line, refline in zip_longest(output, refoutput): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
301 if refline is not None and not isinstance(refline, basestring): |
21 | 302 line = bytes(line, sys.getdefaultencoding()) |
303 if line != refline: | |
22 | 304 raise WrongAnswer |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
305 return 1 |
21 | 306 elif callable(case.validator): |
307 return case.validator(output) | |
308 else: | |
309 # Call the validator program | |
310 output.close() | |
23 | 311 if case.problem.config.ansname: |
312 case.open_outfile() | |
313 case.outfile.copy(case.problem.config.ansname) | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
314 try: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
315 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
|
316 except OSError: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
317 raise CannotStartValidator(sys.exc_info()[1]) |
21 | 318 comment = case.process.communicate()[0].strip() |
26 | 319 match = re.match(r'(?i)(ok|(?:correct|wrong)(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', comment) |
21 | 320 if match: |
321 comment = comment[match.end():] | |
322 if not case.problem.config.maxexitcode: | |
323 if case.process.returncode: | |
324 raise WrongAnswer(comment) | |
325 else: | |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
326 return 1, comment |
21 | 327 else: |
24
c23d81f4a1a3
Score returned by TestCase.__call__() is now normalized to 0..1
Oleg Oshmyan <chortos@inbox.lv>
parents:
23
diff
changeset
|
328 return case.process.returncode / case.problem.config.maxexitcode, comment |
21 | 329 |
330 | |
331 class BatchTestCase(ValidatedTestCase): | |
332 __slots__ = () | |
333 | |
22 | 334 def test(case, callback): |
335 init_canceled() | |
21 | 336 if sys.platform == 'win32' or not case.maxmemory: |
337 preexec_fn = None | |
338 else: | |
339 def preexec_fn(): | |
340 try: | |
341 import resource | |
342 maxmemory = int(case.maxmemory * 1048576) | |
343 resource.setrlimit(resource.RLIMIT_AS, (maxmemory, maxmemory)) | |
344 # I would also set a CPU time limit but I do not want the time | |
345 # that passes between the calls to fork and exec to be counted in | |
346 except MemoryError: | |
347 # We do not have enough memory for ourselves; | |
348 # let the parent know about this | |
349 raise | |
350 except Exception: | |
351 # Well, at least we tried | |
352 pass | |
353 case.open_infile() | |
354 case.time_started = None | |
355 if case.problem.config.stdio: | |
356 if options.erase and not case.validator: | |
22 | 357 # TODO: re-use the same file name if possible |
21 | 358 # FIXME: 2.5 lacks the delete parameter |
359 with tempfile.NamedTemporaryFile(delete=False) as f: | |
22 | 360 inputdatafname = f.name |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
361 contextmgr = CopyDeleting(case, case.infile, inputdatafname) |
21 | 362 else: |
363 inputdatafname = case.problem.config.inname | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
364 contextmgr = Copying(case.infile, inputdatafname) |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
365 with contextmgr: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
366 # FIXME: this U doesn't do anything good for the child process, does it? |
22 | 367 with open(inputdatafname, 'rU') as infile: |
368 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: | |
23 | 369 # TODO: make sure outfile.file is passed to Popen if needed |
21 | 370 try: |
22 | 371 try: |
372 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) | |
373 except MemoryError: | |
374 # If there is not enough memory for the forked test.py, | |
375 # opt for silent dropping of the limit | |
26 | 376 # TODO: show a warning somewhere |
22 | 377 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) |
378 except OSError: | |
379 raise CannotStartTestee(sys.exc_info()[1]) | |
380 case.time_started = clock() | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
381 time_next_check = case.time_started + .15 |
22 | 382 if not case.maxtime: |
383 while True: | |
384 exitcode, now = case.process.poll(), clock() | |
385 if exitcode is not None: | |
386 case.time_stopped = now | |
387 break | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
388 # For some reason (probably Microsoft's fault), |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
389 # msvcrt.kbhit() is slow as hell |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
390 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
391 if now >= time_next_check: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
392 if canceled(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
393 raise CanceledByUser |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
394 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
395 time_next_check = now + .15 |
27 | 396 time.sleep(0) |
22 | 397 else: |
398 time_end = case.time_started + case.maxtime | |
399 while True: | |
400 exitcode, now = case.process.poll(), clock() | |
401 if exitcode is not None: | |
402 case.time_stopped = now | |
403 break | |
404 elif now >= time_end: | |
405 raise TimeLimitExceeded | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
406 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
407 if now >= time_next_check: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
408 if canceled(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
409 raise CanceledByUser |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
410 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
411 time_next_check = now + .15 |
27 | 412 time.sleep(0) |
22 | 413 if config.globalconf.force_zero_exitcode and case.process.returncode: |
414 raise NonZeroExitCode(case.process.returncode) | |
415 callback() | |
416 case.has_called_back = True | |
417 outfile.seek(0) | |
418 return case.validate(outfile) | |
21 | 419 else: |
22 | 420 case.infile.copy(case.problem.config.inname) |
21 | 421 try: |
422 try: | |
423 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) | |
424 except MemoryError: | |
425 # If there is not enough memory for the forked test.py, | |
426 # opt for silent dropping of the limit | |
26 | 427 # TODO: show a warning somewhere |
21 | 428 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) |
429 except OSError: | |
430 raise CannotStartTestee(sys.exc_info()[1]) | |
431 case.time_started = clock() | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
432 time_next_check = case.time_started + .15 |
21 | 433 if not case.maxtime: |
22 | 434 while True: |
435 exitcode, now = case.process.poll(), clock() | |
436 if exitcode is not None: | |
437 case.time_stopped = now | |
438 break | |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
439 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
440 if now >= time_next_check: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
441 if canceled(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
442 raise CanceledByUser |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
443 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
444 time_next_check = now + .15 |
27 | 445 time.sleep(0) |
21 | 446 else: |
447 time_end = case.time_started + case.maxtime | |
448 while True: | |
22 | 449 exitcode, now = case.process.poll(), clock() |
21 | 450 if exitcode is not None: |
451 case.time_stopped = now | |
452 break | |
453 elif now >= time_end: | |
22 | 454 raise TimeLimitExceeded |
25
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
455 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
456 if now >= time_next_check: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
457 if canceled(): |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
458 raise CanceledByUser |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
459 else: |
b500e117080e
Bug fixes and overhead reduction
Oleg Oshmyan <chortos@inbox.lv>
parents:
24
diff
changeset
|
460 time_next_check = now + .15 |
27 | 461 time.sleep(0) |
21 | 462 if config.globalconf.force_zero_exitcode and case.process.returncode: |
463 raise NonZeroExitCode(case.process.returncode) | |
22 | 464 callback() |
465 case.has_called_back = True | |
21 | 466 with open(case.problem.config.outname, 'rU') as output: |
467 return case.validate(output) | |
468 | |
469 | |
470 # This is the only test case type not executing any programs to be tested | |
471 class OutputOnlyTestCase(ValidatedTestCase): | |
472 __slots__ = () | |
473 def cleanup(case): pass | |
474 | |
475 class BestOutputTestCase(ValidatedTestCase): | |
476 __slots__ = () | |
477 | |
478 # This is the only test case type executing two programs simultaneously | |
479 class ReactiveTestCase(TestCase): | |
480 __slots__ = () | |
481 # The basic idea is to launch the program to be tested and the grader | |
482 # and to pipe their standard I/O from and to each other, | |
483 # and then to capture the grader's exit code and use it | |
26 | 484 # like the exit code of an output validator is used. |
21 | 485 |
486 | |
487 def load_problem(prob, _types={'batch' : BatchTestCase, | |
488 'outonly' : OutputOnlyTestCase, | |
489 'bestout' : BestOutputTestCase, | |
490 'reactive': ReactiveTestCase}): | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
491 # 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
|
492 try: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
493 len(prob.config.dummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
494 except Exception: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
495 prob.config.dummies = tuple(prob.config.dummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
496 try: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
497 len(prob.config.tests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
498 except Exception: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
499 prob.config.tests = tuple(prob.config.tests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
500 |
23 | 501 if options.legacy: |
502 prob.config.usegroups = False | |
503 prob.config.tests = list(prob.config.tests) | |
504 for i, name in enumerate(prob.config.tests): | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
505 # Same here; we'll need to iterate over them twice |
23 | 506 try: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
507 l = len(name) |
23 | 508 except Exception: |
509 try: | |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
510 name = tuple(name) |
23 | 511 except TypeError: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
512 name = (name,) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
513 l = len(name) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
514 if len(name) > 1: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
515 prob.config.usegroups = True |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
516 break |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
517 elif not len(name): |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
518 prob.config.tests[i] = (name,) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
519 |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
520 # First get prob.cache.padoutput right, |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
521 # then yield the actual test cases |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
522 for i in prob.config.dummies: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
523 s = 'sample ' + str(i).zfill(prob.config.paddummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
524 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) |
16 | 525 if prob.config.usegroups: |
39
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
526 for group in prob.config.tests: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
527 for i in group: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
528 s = str(i).zfill(prob.config.padtests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
529 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
530 for i in prob.config.dummies: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
531 s = str(i).zfill(prob.config.paddummies) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
532 yield _types[prob.config.kind](prob, s, True, 0) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
533 for group in prob.config.tests: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
534 yield problem.TestGroup() |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
535 for i in group: |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
536 s = str(i).zfill(prob.config.padtests) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
537 yield _types[prob.config.kind](prob, s, False, prob.config.pointmap.get(i, prob.config.pointmap.get(None, prob.config.maxexitcode if prob.config.maxexitcode else 1))) |
2b459f9743b4
Test groups are now supported
Oleg Oshmyan <chortos@inbox.lv>
parents:
27
diff
changeset
|
538 yield problem.test_context_end |
16 | 539 else: |
540 for i in prob.config.tests: | |
21 | 541 s = str(i).zfill(prob.config.padtests) |
542 prob.cache.padoutput = max(prob.cache.padoutput, len(s)) | |
543 for i in prob.config.dummies: | |
544 s = str(i).zfill(prob.config.paddummies) | |
545 yield _types[prob.config.kind](prob, s, True, 0) | |
546 for i in prob.config.tests: | |
547 s = str(i).zfill(prob.config.padtests) | |
548 yield _types[prob.config.kind](prob, s, False, prob.config.pointmap.get(i, prob.config.pointmap.get(None, prob.config.maxexitcode if prob.config.maxexitcode else 1))) |