Mercurial > ~astiob > upreckon > hgweb
comparison testcases.py @ 77:69eadc60f4e2
Memory limit is now applied to the RSS when os.wait4 is available
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Thu, 13 Jan 2011 19:47:35 +0200 |
parents | 0e5ae28e0b2b |
children | ee8a99dcaaed |
comparison
equal
deleted
inserted
replaced
76:0e5ae28e0b2b | 77:69eadc60f4e2 |
---|---|
1 #! /usr/bin/env python | 1 #! /usr/bin/env python |
2 # Copyright (c) 2010 Chortos-2 <chortos@inbox.lv> | 2 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> |
3 | 3 |
4 # TODO: copy the ansfile if not options.erase even if no validator is used | 4 # TODO: copy the ansfile if not options.erase even if no validator is used |
5 | 5 |
6 from __future__ import division, with_statement | 6 from __future__ import division, with_statement |
7 | 7 |
270 del kwargs['preexec_fn'] | 270 del kwargs['preexec_fn'] |
271 return call_real(*args, **kwargs) | 271 return call_real(*args, **kwargs) |
272 else: | 272 else: |
273 return call_real(*args, **kwargs) | 273 return call_real(*args, **kwargs) |
274 | 274 |
275 # Emulate memory limits on platforms compatible with 4.3BSD but not XSI | |
276 # I say 'emulate' because the OS will allow excessive memory usage | |
277 # anyway; Upreckon will just treat the test case as not passed. | |
278 # To do this, we not only require os.wait4 to be present but also | |
279 # assume things about the implementation of subprocess.Popen. | |
280 try: | |
281 def waitpid_emu(pid, options, _wait4=os.wait4): | |
282 global last_rusage | |
283 pid, status, last_rusage = _wait4(pid, options) | |
284 return pid, status | |
285 _waitpid = os.waitpid | |
286 os.waitpid = waitpid_emu | |
287 try: | |
288 defaults = Popen._internal_poll.__func__.__defaults__ | |
289 except AttributeError: | |
290 # Python 2.5 | |
291 defaults = Popen._internal_poll.im_func.func_defaults | |
292 i = defaults.index(_waitpid) | |
293 defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:] | |
294 try: | |
295 Popen._internal_poll.__func__.__defaults__ = defaults | |
296 except AttributeError: | |
297 pass | |
298 Popen._internal_poll.im_func.func_defaults = defaults | |
299 except (AttributeError, ValueError): | |
300 pass | |
275 | 301 |
276 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', | 302 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', |
277 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', | 303 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', |
278 'NonZeroExitCode', 'CannotStartTestee', | 304 'NonZeroExitCode', 'CannotStartTestee', |
279 'CannotStartValidator', 'CannotReadOutputFile', | 305 'CannotStartValidator', 'CannotReadOutputFile', |
283 | 309 |
284 # Exceptions | 310 # Exceptions |
285 | 311 |
286 class TestCaseNotPassed(Exception): __slots__ = () | 312 class TestCaseNotPassed(Exception): __slots__ = () |
287 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () | 313 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () |
314 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = () | |
288 class CanceledByUser(TestCaseNotPassed): __slots__ = () | 315 class CanceledByUser(TestCaseNotPassed): __slots__ = () |
289 | 316 |
290 class WrongAnswer(TestCaseNotPassed): | 317 class WrongAnswer(TestCaseNotPassed): |
291 __slots__ = 'comment' | 318 __slots__ = 'comment' |
292 def __init__(self, comment=''): | 319 def __init__(self, comment=''): |
537 except Exception: | 564 except Exception: |
538 # Well, at least we tried | 565 # Well, at least we tried |
539 pass | 566 pass |
540 case.open_infile() | 567 case.open_infile() |
541 case.time_started = None | 568 case.time_started = None |
569 global last_rusage | |
570 last_rusage = None | |
542 if case.problem.config.stdio: | 571 if case.problem.config.stdio: |
543 if options.erase and not case.validator or not case.problem.config.inname: | 572 if options.erase and not case.validator or not case.problem.config.inname: |
544 # TODO: re-use the same file name if possible | 573 # TODO: re-use the same file name if possible |
545 # FIXME: 2.5 lacks the delete parameter | 574 # FIXME: 2.5 lacks the delete parameter |
546 with tempfile.NamedTemporaryFile(delete=False) as f: | 575 with tempfile.NamedTemporaryFile(delete=False) as f: |
599 else: | 628 else: |
600 time_next_check = now + .15 | 629 time_next_check = now + .15 |
601 time.sleep(.001) | 630 time.sleep(.001) |
602 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: | 631 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
603 raise NonZeroExitCode(case.process.returncode) | 632 raise NonZeroExitCode(case.process.returncode) |
633 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): | |
634 raise MemoryLimitExceeded | |
604 callback() | 635 callback() |
605 case.has_called_back = True | 636 case.has_called_back = True |
606 outfile.seek(0) | 637 outfile.seek(0) |
607 return case.validate(outfile) | 638 return case.validate(outfile) |
608 else: | 639 else: |
651 else: | 682 else: |
652 time_next_check = now + .15 | 683 time_next_check = now + .15 |
653 time.sleep(.001) | 684 time.sleep(.001) |
654 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: | 685 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
655 raise NonZeroExitCode(case.process.returncode) | 686 raise NonZeroExitCode(case.process.returncode) |
687 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): | |
688 raise MemoryLimitExceeded | |
656 callback() | 689 callback() |
657 case.has_called_back = True | 690 case.has_called_back = True |
658 with open(case.problem.config.outname, 'rU') as output: | 691 with open(case.problem.config.outname, 'rU') as output: |
659 return case.validate(output) | 692 return case.validate(output) |
660 | 693 |