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