# HG changeset patch # User Oleg Oshmyan # Date 1292891058 -7200 # Node ID 693a938bdeee62c754d0a18eecce9e12ea8470ec # Parent c726235e49eec46703ab55af10bc013825609ca7 Accurate run-time reporting on POSIX diff -r c726235e49ee -r 693a938bdeee testcases.py --- a/testcases.py Mon Dec 20 23:03:28 2010 +0200 +++ b/testcases.py Tue Dec 21 02:24:18 2010 +0200 @@ -49,7 +49,8 @@ import msvcrt except ImportError: try: - import select, termios, tty, atexit + from select import select + import termios, tty, atexit except ImportError: # It cannot be helped! # Silently disable support for killing the program being tested @@ -61,13 +62,13 @@ atexit.register(cleanup) del cleanup tty.setcbreak(sys.stdin.fileno()) - def canceled(select=select.select, stdin=sys.stdin, read=sys.stdin.read): + def canceled(select=select, stdin=sys.stdin, read=sys.stdin.read): while select((stdin,), (), (), 0)[0]: if read(1) == '\33': return True return False def init_canceled(): - while select.select((sys.stdin,), (), (), 0)[0]: + while select((sys.stdin,), (), (), 0)[0]: sys.stdin.read(1) def pause(): sys.stdin.read(1) @@ -87,6 +88,82 @@ def pause(): msvcrt.getch() +try: + from signal import SIGCHLD, signal, SIG_DFL + from select import select, error as select_error + from errno import EINTR +except ImportError: + try: + import ctypes + WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects + except (ImportError, AttributeError): + pass + else: + # TODO: implement Win32 call() + pass +else: + # Make SIGCHLD interrupt sleep() and select() + def bury_child(signum, frame): + try: + bury_child.case.time_stopped = clock() + except Exception: + pass + signal(SIGCHLD, bury_child) + + # If you want this to work, don't set any stdio argument to PIPE + def call_real(*args, **kwargs): + bury_child.case = case = kwargs.pop('case') + # FIXME: kill the process no matter what + try: + case.process = Popen(*args, **kwargs) + except OSError: + raise CannotStartTestee(sys.exc_info()[1]) + case.time_started = clock() + if pause is None: + if case.maxtime: + time.sleep(case.maxtime) + if case.process.poll() is None: + raise TimeLimitExceeded + else: + case.process.wait() + else: + if not case.maxtime: + try: + while case.process.poll() is None: + if select((sys.stdin,), (), ())[0]: + if sys.stdin.read(1) == '\33': + raise CanceledByUser + except select_error: + if sys.exc_info()[1].args[0] != EINTR: + raise + else: + time_end = clock() + case.maxtime + try: + while case.process.poll() is None: + remaining = time_end - clock() + if remaining > 0: + if select((sys.stdin,), (), (), remaining)[0]: + if sys.stdin.read(1) == '\33': + raise CanceledByUser + else: + raise TimeLimitExceeded + except select_error: + if sys.exc_info()[1].args[0] != EINTR: + raise + del bury_child.case + def call(*args, **kwargs): + if 'preexec_fn' in kwargs: + try: + return call_real(*args, **kwargs) + except MemoryError: + # If there is not enough memory for the forked test.py, + # opt for silent dropping of the limit + # TODO: show a warning somewhere + del kwargs['preexec_fn'] + return call_real(*args, **kwargs) + else: + return call_real(*args, **kwargs) + __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', @@ -369,48 +446,51 @@ with open(inputdatafname, 'rU') as infile: with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: try: + call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) + except NameError: try: - case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) - except MemoryError: - # If there is not enough memory for the forked test.py, - # opt for silent dropping of the limit - # TODO: show a warning somewhere - case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) - except OSError: - raise CannotStartTestee(sys.exc_info()[1]) - case.time_started = clock() - time_next_check = case.time_started + .15 - if not case.maxtime: - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - # For some reason (probably Microsoft's fault), - # msvcrt.kbhit() is slow as hell - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) - else: - time_end = case.time_started + case.maxtime - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - elif now >= time_end: - raise TimeLimitExceeded - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) + try: + case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) + except MemoryError: + # If there is not enough memory for the forked test.py, + # opt for silent dropping of the limit + # TODO: show a warning somewhere + case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) + except OSError: + raise CannotStartTestee(sys.exc_info()[1]) + case.time_started = clock() + time_next_check = case.time_started + .15 + if not case.maxtime: + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + # For some reason (probably Microsoft's fault), + # msvcrt.kbhit() is slow as hell + else: + if now >= time_next_check: + if canceled(): + raise CanceledByUser + else: + time_next_check = now + .15 + time.sleep(.001) + else: + time_end = case.time_started + case.maxtime + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + elif now >= time_end: + raise TimeLimitExceeded + else: + if now >= time_next_check: + if canceled(): + raise CanceledByUser + else: + time_next_check = now + .15 + time.sleep(.001) if config.globalconf.force_zero_exitcode and case.process.returncode: raise NonZeroExitCode(case.process.returncode) callback() @@ -420,46 +500,49 @@ else: case.infile.copy(case.problem.config.inname) try: + call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) + except NameError: try: - case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) - except MemoryError: - # If there is not enough memory for the forked test.py, - # opt for silent dropping of the limit - # TODO: show a warning somewhere - case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) - except OSError: - raise CannotStartTestee(sys.exc_info()[1]) - case.time_started = clock() - time_next_check = case.time_started + .15 - if not case.maxtime: - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) - else: - time_end = case.time_started + case.maxtime - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - elif now >= time_end: - raise TimeLimitExceeded - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) + try: + case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) + except MemoryError: + # If there is not enough memory for the forked test.py, + # opt for silent dropping of the limit + # TODO: show a warning somewhere + case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) + except OSError: + raise CannotStartTestee(sys.exc_info()[1]) + case.time_started = clock() + time_next_check = case.time_started + .15 + if not case.maxtime: + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + else: + if now >= time_next_check: + if canceled(): + raise CanceledByUser + else: + time_next_check = now + .15 + time.sleep(.001) + else: + time_end = case.time_started + case.maxtime + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + elif now >= time_end: + raise TimeLimitExceeded + else: + if now >= time_next_check: + if canceled(): + raise CanceledByUser + else: + time_next_check = now + .15 + time.sleep(.001) if config.globalconf.force_zero_exitcode and case.process.returncode: raise NonZeroExitCode(case.process.returncode) callback()