comparison testcases.py @ 56:693a938bdeee

Accurate run-time reporting on POSIX
author Oleg Oshmyan <chortos@inbox.lv>
date Tue, 21 Dec 2010 02:24:18 +0200
parents c726235e49ee
children 855bdfeb32a6
comparison
equal deleted inserted replaced
55:c726235e49ee 56:693a938bdeee
47 try: 47 try:
48 # Windows has select() too, but it is not the select() we want 48 # Windows has select() too, but it is not the select() we want
49 import msvcrt 49 import msvcrt
50 except ImportError: 50 except ImportError:
51 try: 51 try:
52 import select, termios, tty, atexit 52 from select import select
53 import termios, tty, atexit
53 except ImportError: 54 except ImportError:
54 # It cannot be helped! 55 # It cannot be helped!
55 # Silently disable support for killing the program being tested 56 # Silently disable support for killing the program being tested
56 canceled = init_canceled = lambda: False 57 canceled = init_canceled = lambda: False
57 pause = None 58 pause = None
59 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): 60 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
60 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) 61 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
61 atexit.register(cleanup) 62 atexit.register(cleanup)
62 del cleanup 63 del cleanup
63 tty.setcbreak(sys.stdin.fileno()) 64 tty.setcbreak(sys.stdin.fileno())
64 def canceled(select=select.select, stdin=sys.stdin, read=sys.stdin.read): 65 def canceled(select=select, stdin=sys.stdin, read=sys.stdin.read):
65 while select((stdin,), (), (), 0)[0]: 66 while select((stdin,), (), (), 0)[0]:
66 if read(1) == '\33': 67 if read(1) == '\33':
67 return True 68 return True
68 return False 69 return False
69 def init_canceled(): 70 def init_canceled():
70 while select.select((sys.stdin,), (), (), 0)[0]: 71 while select((sys.stdin,), (), (), 0)[0]:
71 sys.stdin.read(1) 72 sys.stdin.read(1)
72 def pause(): 73 def pause():
73 sys.stdin.read(1) 74 sys.stdin.read(1)
74 else: 75 else:
75 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): 76 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch):
84 def init_canceled(): 85 def init_canceled():
85 while msvcrt.kbhit(): 86 while msvcrt.kbhit():
86 msvcrt.getch() 87 msvcrt.getch()
87 def pause(): 88 def pause():
88 msvcrt.getch() 89 msvcrt.getch()
90
91 try:
92 from signal import SIGCHLD, signal, SIG_DFL
93 from select import select, error as select_error
94 from errno import EINTR
95 except ImportError:
96 try:
97 import ctypes
98 WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects
99 except (ImportError, AttributeError):
100 pass
101 else:
102 # TODO: implement Win32 call()
103 pass
104 else:
105 # Make SIGCHLD interrupt sleep() and select()
106 def bury_child(signum, frame):
107 try:
108 bury_child.case.time_stopped = clock()
109 except Exception:
110 pass
111 signal(SIGCHLD, bury_child)
112
113 # If you want this to work, don't set any stdio argument to PIPE
114 def call_real(*args, **kwargs):
115 bury_child.case = case = kwargs.pop('case')
116 # FIXME: kill the process no matter what
117 try:
118 case.process = Popen(*args, **kwargs)
119 except OSError:
120 raise CannotStartTestee(sys.exc_info()[1])
121 case.time_started = clock()
122 if pause is None:
123 if case.maxtime:
124 time.sleep(case.maxtime)
125 if case.process.poll() is None:
126 raise TimeLimitExceeded
127 else:
128 case.process.wait()
129 else:
130 if not case.maxtime:
131 try:
132 while case.process.poll() is None:
133 if select((sys.stdin,), (), ())[0]:
134 if sys.stdin.read(1) == '\33':
135 raise CanceledByUser
136 except select_error:
137 if sys.exc_info()[1].args[0] != EINTR:
138 raise
139 else:
140 time_end = clock() + case.maxtime
141 try:
142 while case.process.poll() is None:
143 remaining = time_end - clock()
144 if remaining > 0:
145 if select((sys.stdin,), (), (), remaining)[0]:
146 if sys.stdin.read(1) == '\33':
147 raise CanceledByUser
148 else:
149 raise TimeLimitExceeded
150 except select_error:
151 if sys.exc_info()[1].args[0] != EINTR:
152 raise
153 del bury_child.case
154 def call(*args, **kwargs):
155 if 'preexec_fn' in kwargs:
156 try:
157 return call_real(*args, **kwargs)
158 except MemoryError:
159 # If there is not enough memory for the forked test.py,
160 # opt for silent dropping of the limit
161 # TODO: show a warning somewhere
162 del kwargs['preexec_fn']
163 return call_real(*args, **kwargs)
164 else:
165 return call_real(*args, **kwargs)
89 166
90 167
91 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', 168 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed',
92 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', 169 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer',
93 'NonZeroExitCode', 'CannotStartTestee', 170 'NonZeroExitCode', 'CannotStartTestee',
367 with contextmgr: 444 with contextmgr:
368 # FIXME: this U doesn't do anything good for the child process, does it? 445 # FIXME: this U doesn't do anything good for the child process, does it?
369 with open(inputdatafname, 'rU') as infile: 446 with open(inputdatafname, 'rU') as infile:
370 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: 447 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile:
371 try: 448 try:
449 call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn)
450 except NameError:
372 try: 451 try:
373 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) 452 try:
374 except MemoryError: 453 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn)
375 # If there is not enough memory for the forked test.py, 454 except MemoryError:
376 # opt for silent dropping of the limit 455 # If there is not enough memory for the forked test.py,
377 # TODO: show a warning somewhere 456 # opt for silent dropping of the limit
378 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) 457 # TODO: show a warning somewhere
379 except OSError: 458 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1)
380 raise CannotStartTestee(sys.exc_info()[1]) 459 except OSError:
381 case.time_started = clock() 460 raise CannotStartTestee(sys.exc_info()[1])
382 time_next_check = case.time_started + .15 461 case.time_started = clock()
383 if not case.maxtime: 462 time_next_check = case.time_started + .15
384 while True: 463 if not case.maxtime:
385 exitcode, now = case.process.poll(), clock() 464 while True:
386 if exitcode is not None: 465 exitcode, now = case.process.poll(), clock()
387 case.time_stopped = now 466 if exitcode is not None:
388 break 467 case.time_stopped = now
389 # For some reason (probably Microsoft's fault), 468 break
390 # msvcrt.kbhit() is slow as hell 469 # For some reason (probably Microsoft's fault),
391 else: 470 # msvcrt.kbhit() is slow as hell
392 if now >= time_next_check: 471 else:
393 if canceled(): 472 if now >= time_next_check:
394 raise CanceledByUser 473 if canceled():
395 else: 474 raise CanceledByUser
396 time_next_check = now + .15 475 else:
397 time.sleep(.001) 476 time_next_check = now + .15
398 else: 477 time.sleep(.001)
399 time_end = case.time_started + case.maxtime 478 else:
400 while True: 479 time_end = case.time_started + case.maxtime
401 exitcode, now = case.process.poll(), clock() 480 while True:
402 if exitcode is not None: 481 exitcode, now = case.process.poll(), clock()
403 case.time_stopped = now 482 if exitcode is not None:
404 break 483 case.time_stopped = now
405 elif now >= time_end: 484 break
406 raise TimeLimitExceeded 485 elif now >= time_end:
407 else: 486 raise TimeLimitExceeded
408 if now >= time_next_check: 487 else:
409 if canceled(): 488 if now >= time_next_check:
410 raise CanceledByUser 489 if canceled():
411 else: 490 raise CanceledByUser
412 time_next_check = now + .15 491 else:
413 time.sleep(.001) 492 time_next_check = now + .15
493 time.sleep(.001)
414 if config.globalconf.force_zero_exitcode and case.process.returncode: 494 if config.globalconf.force_zero_exitcode and case.process.returncode:
415 raise NonZeroExitCode(case.process.returncode) 495 raise NonZeroExitCode(case.process.returncode)
416 callback() 496 callback()
417 case.has_called_back = True 497 case.has_called_back = True
418 outfile.seek(0) 498 outfile.seek(0)
419 return case.validate(outfile) 499 return case.validate(outfile)
420 else: 500 else:
421 case.infile.copy(case.problem.config.inname) 501 case.infile.copy(case.problem.config.inname)
422 try: 502 try:
423 try: 503 call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn)
424 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) 504 except NameError:
425 except MemoryError: 505 try:
426 # If there is not enough memory for the forked test.py, 506 try:
427 # opt for silent dropping of the limit 507 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn)
428 # TODO: show a warning somewhere 508 except MemoryError:
429 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) 509 # If there is not enough memory for the forked test.py,
430 except OSError: 510 # opt for silent dropping of the limit
431 raise CannotStartTestee(sys.exc_info()[1]) 511 # TODO: show a warning somewhere
432 case.time_started = clock() 512 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT)
433 time_next_check = case.time_started + .15 513 except OSError:
434 if not case.maxtime: 514 raise CannotStartTestee(sys.exc_info()[1])
435 while True: 515 case.time_started = clock()
436 exitcode, now = case.process.poll(), clock() 516 time_next_check = case.time_started + .15
437 if exitcode is not None: 517 if not case.maxtime:
438 case.time_stopped = now 518 while True:
439 break 519 exitcode, now = case.process.poll(), clock()
440 else: 520 if exitcode is not None:
441 if now >= time_next_check: 521 case.time_stopped = now
442 if canceled(): 522 break
443 raise CanceledByUser 523 else:
444 else: 524 if now >= time_next_check:
445 time_next_check = now + .15 525 if canceled():
446 time.sleep(.001) 526 raise CanceledByUser
447 else: 527 else:
448 time_end = case.time_started + case.maxtime 528 time_next_check = now + .15
449 while True: 529 time.sleep(.001)
450 exitcode, now = case.process.poll(), clock() 530 else:
451 if exitcode is not None: 531 time_end = case.time_started + case.maxtime
452 case.time_stopped = now 532 while True:
453 break 533 exitcode, now = case.process.poll(), clock()
454 elif now >= time_end: 534 if exitcode is not None:
455 raise TimeLimitExceeded 535 case.time_stopped = now
456 else: 536 break
457 if now >= time_next_check: 537 elif now >= time_end:
458 if canceled(): 538 raise TimeLimitExceeded
459 raise CanceledByUser 539 else:
460 else: 540 if now >= time_next_check:
461 time_next_check = now + .15 541 if canceled():
462 time.sleep(.001) 542 raise CanceledByUser
543 else:
544 time_next_check = now + .15
545 time.sleep(.001)
463 if config.globalconf.force_zero_exitcode and case.process.returncode: 546 if config.globalconf.force_zero_exitcode and case.process.returncode:
464 raise NonZeroExitCode(case.process.returncode) 547 raise NonZeroExitCode(case.process.returncode)
465 callback() 548 callback()
466 case.has_called_back = True 549 case.has_called_back = True
467 with open(case.problem.config.outname, 'rU') as output: 550 with open(case.problem.config.outname, 'rU') as output: