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