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: |