changeset 82:06356af50bf9

Finished testcases reorganization and CPU time limit implementation We now have: * Win32-specific code in the win32 module (including bug fixes), * UNIX-specific and generic code in the unix module, * a much cleaner testcases module, * wait4-based resource limits working on Python 3 (this is a bug fix), * no warning/error reported on non-Win32 when -x is not passed but standard input does not come from a terminal, * the maxtime configuration variable replaced with two new variables named maxcputime and maxwalltime, * CPU time reported if it can be determined unless an error occurs sooner than it is determined (e. g. if the wall-clock time limit is exceeded), * memory limits enforced even if Upreckon's forking already breaks them, * CPU time limits and private virtual memory limits honoured on Win32, * CPU time limits honoured on UNIX(-like) platforms supporting wait4 or getrusage, * address space limits honoured on UNIX(-like) platforms supporting setrlimit with RLIMIT_AS/RLIMIT_VMEM, * resident set size limits honoured on UNIX(-like) platforms supporting wait4.
author Oleg Oshmyan <chortos@inbox.lv>
date Wed, 23 Feb 2011 23:35:27 +0000
parents 24752db487c5
children 37c4ad87583c
files config.py problem.py testcases.py unix.py win32.py
diffstat 5 files changed, 308 insertions(+), 450 deletions(-) [+]
line wrap: on
line diff
--- a/config.py	Wed Feb 16 15:30:57 2011 +0000
+++ b/config.py	Wed Feb 23 23:35:27 2011 +0000
@@ -26,7 +26,8 @@
 
 defaults_problem = {'kind': 'batch',
                     'usegroups': False,
-                    'maxtime': None,
+                    'maxcputime': None,
+                    'maxwalltime': None,
                     'maxmemory': None,
                     'dummies': {},
                     'testsexcluded': (),
@@ -155,7 +156,7 @@
 						newmap[key] = oldmap[key]
 			setattr(module, name, newmap)
 	if options.no_maxtime:
-		module.maxtime = 0
+		module.maxcputime = module.maxwalltime = 0
 	sys.dont_write_bytecode = dwb
 	for name in patterns:
 		if hasattr(module, name):
--- a/problem.py	Wed Feb 16 15:30:57 2011 +0000
+++ b/problem.py	Wed Feb 23 23:35:27 2011 +0000
@@ -141,8 +141,10 @@
 					granted = case(lambda: (say('%7.3f%s s, ' % (case.time_stopped - case.time_started, case.time_limit_string), end=''), sys.stdout.flush()))
 				except testcases.CanceledByUser:
 					verdict = 'canceled by the user'
-				except testcases.TimeLimitExceeded:
-					verdict = 'time limit exceeded'
+				except testcases.WallTimeLimitExceeded:
+					verdict = 'wall-clock time limit exceeded'
+				except testcases.CPUTimeLimitExceeded:
+					verdict = 'CPU time limit exceeded'
 				except testcases.MemoryLimitExceeded:
 					verdict = 'memory limit exceeded'
 				except testcases.WrongAnswer:
--- a/testcases.py	Wed Feb 16 15:30:57 2011 +0000
+++ b/testcases.py	Wed Feb 23 23:35:27 2011 +0000
@@ -21,290 +21,17 @@
 devnull = open(os.path.devnull, 'w+')
 
 try:
-	from signal import SIGTERM, SIGKILL
-except ImportError:
-	SIGTERM = 15
-	SIGKILL = 9
-
-try:
-	from _subprocess import TerminateProcess
-except ImportError:
-	# CPython 2.5 does define _subprocess.TerminateProcess even though it is
-	# not used in the subprocess module, but maybe something else does not
-	try:
-		import ctypes
-		TerminateProcess = ctypes.windll.kernel32.TerminateProcess
-	except (ImportError, AttributeError):
-		TerminateProcess = None
-
-
-# Do not show error messages due to errors in the program being tested
-try:
-	import ctypes
-	try:
-		errmode = ctypes.windll.kernel32.GetErrorMode()
-	except AttributeError:
-		errmode = ctypes.windll.kernel32.SetErrorMode(0)
-	errmode |= 0x8003
-	ctypes.windll.kernel32.SetErrorMode(errmode)
+	from win32 import *
 except Exception:
-	pass
-
-
-# Do the hacky-wacky dark magic needed to catch presses of the Escape button.
-# If only Python supported forcible termination of threads...
-if not sys.stdin.isatty():
-	canceled = init_canceled = lambda: False
-	pause = None
-else:
-	try:
-		# Windows has select() too, but it is not the select() we want
-		import msvcrt
-	except ImportError:
-		try:
-			from select import select
-			import termios, tty, atexit
-		except ImportError:
-			# It cannot be helped!
-			# Silently disable support for killing the program being tested
-			canceled = init_canceled = lambda: False
-			pause = None
-		else:
-			def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
-				termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
-			atexit.register(cleanup)
-			del cleanup
-			tty.setcbreak(sys.stdin.fileno())
-			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((sys.stdin,), (), (), 0)[0]:
-					sys.stdin.read(1)
-			def pause():
-				sys.stdin.read(1)
-	else:
-		def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch):
-			while kbhit():
-				c = getch()
-				if c == '\33':
-					return True
-				elif c == '\0':
-					# Let's hope no-one is fiddling with this
-					getch()
-			return False
-		def init_canceled():
-			while msvcrt.kbhit():
-				msvcrt.getch()
-		def pause():
-			msvcrt.getch()
-
-try:
-	from signal import SIGCHLD, signal, SIG_DFL
-	from select import select, error as select_error
-	from errno import EINTR
-	import fcntl
-	try:
-		import cPickle as pickle
-	except ImportError:
-		import pickle
-except ImportError:
-	try:
-		from _subprocess import WAIT_OBJECT_0, STD_INPUT_HANDLE, INFINITE
-	except ImportError:
-		WAIT_OBJECT_0 = 0
-		STD_INPUT_HANDLE = -10
-		INFINITE = -1
-	try:
-		import ctypes
-		SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
-		FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
-		WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects
-		ReadConsoleInputA = ctypes.windll.kernel32.ReadConsoleInputA
-		try:
-			from _subprocess import GetStdHandle
-		except ImportError:
-			GetStdHandle = ctypes.windll.kernel32.GetStdHandle
-	except (ImportError, AttributeError):
-		console_input = False
-	else:
-		hStdin = GetStdHandle(STD_INPUT_HANDLE)
-		console_input = bool(SetConsoleMode(hStdin, 1))
-		if console_input:
-			FlushConsoleInputBuffer(hStdin)
-			class KEY_EVENT_RECORD(ctypes.Structure):
-				_fields_ = (("bKeyDown", ctypes.c_int),
-					        ("wRepeatCount", ctypes.c_ushort),
-					        ("wVirtualKeyCode", ctypes.c_ushort),
-					        ("wVirtualScanCode", ctypes.c_ushort),
-					        ("UnicodeChar", ctypes.c_wchar),
-					        ("dwControlKeyState", ctypes.c_uint))
-			class INPUT_RECORD(ctypes.Structure):
-				_fields_ = (("EventType", ctypes.c_int),
-					        ("KeyEvent", KEY_EVENT_RECORD))
-	# Memory limits (currently) are not supported
-	def call(*args, **kwargs):
-		case = kwargs.pop('case')
-		try:
-			case.process = Popen(*args, **kwargs)
-		except OSError:
-			raise CannotStartTestee(sys.exc_info()[1])
-		case.time_started = clock()
-		if not console_input:
-			if case.maxtime:
-				if WaitForSingleObject(case.process._handle, int(case.maxtime * 1000)) != WAIT_OBJECT_0:
-					raise TimeLimitExceeded
-			else:
-				case.process.wait()
-		else:
-			ir = INPUT_RECORD()
-			n = ctypes.c_int()
-			lpHandles = (ctypes.c_int * 2)(hStdin, case.process._handle)
-			if case.maxtime:
-				time_end = clock() + case.maxtime
-				while case.process.poll() is None:
-					remaining = time_end - clock()
-					if remaining > 0:
-						if WaitForMultipleObjects(2, lpHandles, False, int(remaining * 1000)) == WAIT_OBJECT_0:
-							ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n))
-							if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27:
-								raise CanceledByUser
-					else:
-						raise TimeLimitExceeded
-			else:
-				while case.process.poll() is None:
-					if WaitForMultipleObjects(2, lpHandles, False, INFINITE) == WAIT_OBJECT_0:
-						ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n))
-						if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27:
-							raise CanceledByUser
-		case.time_stopped = clock()
-	if not console_input:
-		try:
-			try:
-				from _subprocess import WaitForSingleObject
-			except ImportError:
-				import ctypes
-				WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
-		except (ImportError, AttributeError):
-			# TODO: move the default implementation here
-			call = None
-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')
-		preexec_fn_ = kwargs.get('preexec_fn', None)
-		read, write = os.pipe()
-		def preexec_fn():
-			os.close(read)
-			if preexec_fn_:
-				preexec_fn_()
-			fcntl.fcntl(write, fcntl.F_SETFD, fcntl.fcntl(write, fcntl.F_GETFD) | getattr(fcntl, 'FD_CLOEXEC', 1))
-			fwrite = os.fdopen(write, 'wb')
-			pickle.dump(clock(), fwrite, 1)
-		kwargs['preexec_fn'] = preexec_fn
-		try:
-			case.process = Popen(*args, **kwargs)
-		except OSError:
-			os.close(read)
-			raise CannotStartTestee(sys.exc_info()[1])
-		finally:
-			os.close(write)
-		try:
-			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:
-							case.process.poll()
-				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
-						else:
-							case.process.poll()
-		finally:
-			case.time_started = pickle.loads(os.read(read, 512))
-			os.close(read)
-			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)
-
-# Emulate memory limits on platforms compatible with 4.3BSD but not XSI
-# I say 'emulate' because the OS will allow excessive memory usage
-# anyway; Upreckon will just treat the test case as not passed.
-# To do this, we not only require os.wait4 to be present but also
-# assume things about the implementation of subprocess.Popen.
-try:
-	def waitpid_emu(pid, options, _wait4=os.wait4):
-		global last_rusage
-		pid, status, last_rusage = _wait4(pid, options)
-		return pid, status
-	_waitpid = os.waitpid
-	os.waitpid = waitpid_emu
-	try:
-		defaults = Popen._internal_poll.__func__.__defaults__
-	except AttributeError:
-		# Python 2.5
-		defaults = Popen._internal_poll.im_func.func_defaults
-	i = defaults.index(_waitpid)
-	defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:]
-	try:
-		Popen._internal_poll.__func__.__defaults__ = defaults
-	except AttributeError:
-		pass
-	Popen._internal_poll.im_func.func_defaults = defaults
-except (AttributeError, ValueError):
-	pass
+	from unix import *
 
 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed',
            'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer',
            'NonZeroExitCode', 'CannotStartTestee',
            'CannotStartValidator', 'CannotReadOutputFile',
            'CannotReadInputFile', 'CannotReadAnswerFile',
-           'MemoryLimitExceeded')
+           'MemoryLimitExceeded', 'CPUTimeLimitExceeded',
+           'WallTimeLimitExceeded')
 
 
 
@@ -312,6 +39,8 @@
 
 class TestCaseNotPassed(Exception): __slots__ = ()
 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = ()
+class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
+class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = ()
 class CanceledByUser(TestCaseNotPassed): __slots__ = ()
 
@@ -384,9 +113,11 @@
 
 class TestCase(object):
 	__slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points',
-	             'process', 'time_started', 'time_stopped', 'time_limit_string',
-	             'realinname', 'realoutname', 'maxtime', 'maxmemory',
-	             'has_called_back', 'files_to_delete')
+	             'process', 'time_started', 'time_stopped',
+	             'realinname', 'realoutname', 'maxcputime', 'maxwalltime',
+	             'maxmemory', 'has_called_back', 'files_to_delete',
+	             'cpu_time_limit_string', 'wall_time_limit_string',
+	             'time_limit_string')
 	
 	if ABCMeta:
 		__metaclass__ = ABCMeta
@@ -396,12 +127,17 @@
 		case.id = id
 		case.isdummy = isdummy
 		case.points = points
-		case.maxtime = case.problem.config.maxtime
+		case.maxcputime = case.problem.config.maxcputime
+		case.maxwalltime = case.problem.config.maxwalltime
 		case.maxmemory = case.problem.config.maxmemory
-		if case.maxtime:
-			case.time_limit_string = '/%.3f' % case.maxtime
+		if case.maxcputime:
+			case.cpu_time_limit_string = '/%.3f' % case.maxcputime
 		else:
-			case.time_limit_string = ''
+			case.cpu_time_limit_string = ''
+		if case.maxwalltime:
+			case.wall_time_limit_string = '/%.3f' % case.maxwalltime
+		else:
+			case.wall_time_limit_string = ''
 		if not isdummy:
 			case.realinname = case.problem.config.testcaseinname
 			case.realoutname = case.problem.config.testcaseoutname
@@ -415,13 +151,14 @@
 	def __call__(case, callback):
 		case.has_called_back = False
 		case.files_to_delete = []
+		case.time_limit_string = case.wall_time_limit_string
 		try:
 			return case.test(callback)
 		finally:
 			now = clock()
-			if not getattr(case, 'time_started', None):
+			if getattr(case, 'time_started', None) is None:
 				case.time_started = case.time_stopped = now
-			elif not getattr(case, 'time_stopped', None):
+			elif getattr(case, 'time_stopped', None) is None:
 				case.time_stopped = now
 			if not case.has_called_back:
 				callback()
@@ -432,21 +169,11 @@
 		#	case.infile.close()
 		#if getattr(case, 'outfile', None):
 		#	case.outfile.close()
-		if getattr(case, 'process', None):
-			# Try killing after three unsuccessful TERM attempts in a row
-			# (except on Windows, where TERMing is killing)
+		if getattr(case, 'process', None) and case.process.returncode is None:
+			# Try KILLing after three unsuccessful TERM attempts in a row
 			for i in range(3):
 				try:
-					try:
-						case.process.terminate()
-					except AttributeError:
-						# Python 2.5
-						if TerminateProcess and hasattr(proc, '_handle'):
-							# Windows API
-							TerminateProcess(proc._handle, 1)
-						else:
-							# POSIX
-							os.kill(proc.pid, SIGTERM)
+					terminate(case.process)
 				except Exception:
 					time.sleep(0)
 					case.process.poll()
@@ -458,16 +185,7 @@
 				# just silently stop trying
 				for i in range(3):
 					try:
-						try:
-							case.process.kill()
-						except AttributeError:
-							# Python 2.5
-							if TerminateProcess and hasattr(proc, '_handle'):
-								# Windows API
-								TerminateProcess(proc._handle, 1)
-							else:
-								# POSIX
-								os.kill(proc.pid, SIGKILL)
+						kill(case.process)
 					except Exception:
 						time.sleep(0)
 						case.process.poll()
@@ -547,28 +265,8 @@
 	__slots__ = ()
 	
 	def test(case, callback):
-		init_canceled()
-		if sys.platform == 'win32' or not case.maxmemory:
-			preexec_fn = None
-		else:
-			def preexec_fn():
-				try:
-					import resource
-					maxmemory = int(case.maxmemory * 1048576)
-					resource.setrlimit(resource.RLIMIT_AS, (maxmemory, maxmemory))
-					# I would also set a CPU time limit but I do not want the time
-					# that passes between the calls to fork and exec to be counted in
-				except MemoryError:
-					# We do not have enough memory for ourselves;
-					# let the parent know about this
-					raise
-				except Exception:
-					# Well, at least we tried
-					pass
 		case.open_infile()
 		case.time_started = None
-		global last_rusage
-		last_rusage = None
 		if case.problem.config.stdio:
 			if options.erase and not case.validator or not case.problem.config.inname:
 				# TODO: re-use the same file name if possible
@@ -582,110 +280,18 @@
 			with contextmgr:
 				with open(inputdatafname) as infile:
 					with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile:
-						if call is not None:
-							call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn)
-						else:
-							try:
-								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)
+						call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1)
 						if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0:
 							raise NonZeroExitCode(case.process.returncode)
-						if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576):
-							raise MemoryLimitExceeded
 						callback()
 						case.has_called_back = True
 						outfile.seek(0)
 						return case.validate(outfile)
 		else:
 			case.infile.copy(case.problem.config.inname)
-			if call is not None:
-				call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn)
-			else:
-				try:
-					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)
+			call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT)
 			if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0:
 				raise NonZeroExitCode(case.process.returncode)
-			if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576):
-				raise MemoryLimitExceeded
 			callback()
 			case.has_called_back = True
 			with open(case.problem.config.outname, 'rU') as output:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/unix.py	Wed Feb 23 23:35:27 2011 +0000
@@ -0,0 +1,239 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+import sys
+
+try:
+	from compat import *
+	import testcases  # mutual import
+except ImportError:
+	import __main__
+	__main__.import_error(sys.exc_info()[1])
+
+from __main__ import clock
+from subprocess import Popen
+import os, sys
+
+try:
+	from signal import SIGTERM, SIGKILL
+except ImportError:
+	SIGTERM = 15
+	SIGKILL = 9
+
+__all__ = 'call', 'kill', 'terminate', 'pause'
+
+
+if not sys.stdin.isatty():
+	pause = lambda: sys.stdin.read(1)
+	catch_escape = False
+else:
+	try:
+		from select import select
+		import termios, tty, atexit
+	except ImportError:
+		pause = None
+		catch_escape = False
+	else:
+		catch_escape = True
+		def cleanup(old=termios.tcgetattr(sys.stdin.fileno())):
+			termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old)
+		atexit.register(cleanup)
+		tty.setcbreak(sys.stdin.fileno())
+		def pause():
+			sys.stdin.read(1)
+
+try:
+	from signal import SIGCHLD, signal, SIG_DFL
+	from select import select, error as SelectError
+	from errno import EINTR
+	from fcntl import fcntl, F_SETFD, F_GETFD
+	try:
+		import cPickle as pickle
+	except ImportError:
+		import pickle
+except ImportError:
+	def call(*args, **kwargs):
+		case = kwargs.pop('case')
+		try:
+			case.process = Popen(*args, **kwargs)
+		except OSError:
+			raise CannotStartTestee(sys.exc_info()[1])
+		case.time_started = clock()
+		if not case.maxtime:
+			while True:
+				exitcode, now = case.process.poll(), clock()
+				if exitcode is not None:
+					case.time_stopped = now
+					break
+				else:
+					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:
+					time.sleep(.001)
+else:
+	try:
+		from fcntl import FD_CLOEXEC
+	except ImportError:
+		FD_CLOEXEC = 1
+	
+	try:
+		from resource import getrusage, RUSAGE_SELF, RUSAGE_CHILDREN
+	except ImportError:
+		from time import clock as cpuclock
+		getrusage = lambda who: None
+	else:
+		def cpuclock():
+			rusage = getrusage(RUSAGE_SELF)
+			return rusage.ru_utime + rusage.ru_stime
+	
+	try:
+		from resource import setrlimit
+		try:
+			from resource import RLIMIT_AS
+		except ImportError:
+			from resource import RLIMIT_VMEM
+	except ImportError:
+		setrlimit = None
+	
+	# 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 portably, don't set any stdio argument to PIPE
+	def call(*args, **kwargs):
+		global last_rusage
+		bury_child.case = case = kwargs.pop('case')
+		read, write = os.pipe()
+		fcntl(write, F_SETFD, fcntl(write, F_GETFD) | FD_CLOEXEC)
+		def preexec_fn():
+			os.close(read)
+			if setrlimit and case.maxmemory:
+				maxmemory = ceil(case.maxmemory * 1048576)
+				setrlimit(RLIMIT_AS, (maxmemory, maxmemory))
+				# I would also set a CPU time limit but I do not want the time
+				# passing between the calls to fork and exec to be counted in
+			os.write(write, pickle.dumps((clock(), cpuclock()), 1))
+		kwargs['preexec_fn'] = preexec_fn
+		old_rusage = getrusage(RUSAGE_CHILDREN)
+		last_rusage = None
+		try:
+			case.process = Popen(*args, **kwargs)
+		except OSError:
+			os.close(read)
+			raise testcases.CannotStartTestee(sys.exc_info()[1])
+		finally:
+			os.close(write)
+		try:
+			if not catch_escape:
+				if case.maxwalltime:
+					time.sleep(case.maxwalltime)
+					if case.process.poll() is None:
+						raise testcases.WallTimeLimitExceeded
+				else:
+					case.process.wait()
+			else:
+				if not case.maxwalltime:
+					try:
+						while case.process.poll() is None:
+							if select((sys.stdin,), (), ())[0]:
+								if sys.stdin.read(1) == '\33':
+									raise testcases.CanceledByUser
+					except SelectError:
+						if sys.exc_info()[1].args[0] != EINTR:
+							raise
+						else:
+							case.process.poll()
+				else:
+					time_end = clock() + case.maxwalltime
+					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 testcases.CanceledByUser
+							else:
+								raise testcases.WallTimeLimitExceeded
+					except SelectError:
+						if sys.exc_info()[1].args[0] != EINTR:
+							raise
+						else:
+							case.process.poll()
+		finally:
+			case.time_started, cpustart = pickle.loads(os.read(read, 512))
+			os.close(read)
+			del bury_child.case
+		new_rusage = getrusage(RUSAGE_CHILDREN)
+		if new_rusage and (case.maxcputime or not case.maxwalltime):
+			case.time_started = cpustart
+			case.time_stopped = new_rusage.ru_utime + new_rusage.ru_stime
+			case.time_limit_string = case.cpu_time_limit_string
+		if case.maxcputime and new_rusage:
+			oldtime = old_rusage.ru_utime + old_rusage.ru_stime
+			newtime = new_rusage.ru_utime + new_rusage.ru_stime
+			if newtime - oldtime - cpustart > case.maxcputime:
+				raise testcases.CPUTimeLimitExceeded
+		if case.maxmemory:
+			if sys.platform != 'darwin':
+				maxrss = case.maxmemory * 1024
+			else:
+				maxrss = case.maxmemory * 1048576
+			if last_rusage and last_rusage.ru_maxrss > maxrss:
+				raise testcases.MemoryLimitExceeded
+			elif (new_rusage and
+			      new_rusage.ru_maxrss > old_rusage.ru_maxrss and
+			      new_rusage.ru_maxrss > maxrss):
+				raise testcases.MemoryLimitExceeded
+
+# Emulate memory limits on platforms compatible with 4.3BSD but not XSI
+# I say 'emulate' because the OS will allow excessive memory usage
+# anyway; Upreckon will just treat the test case as not passed.
+# To do this, we not only require os.wait4 to be present but also
+# assume things about the implementation of subprocess.Popen.
+try:
+	def waitpid_emu(pid, options, _wait4=os.wait4):
+		global last_rusage
+		pid, status, last_rusage = _wait4(pid, options)
+		return pid, status
+	_waitpid = os.waitpid
+	os.waitpid = waitpid_emu
+	try:
+		defaults = Popen._internal_poll.__func__.__defaults__
+	except AttributeError:
+		# Python 2.5
+		defaults = Popen._internal_poll.im_func.func_defaults
+	i = defaults.index(_waitpid)
+	defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:]
+	try:
+		Popen._internal_poll.__func__.__defaults__ = defaults
+	except AttributeError:
+		# Python 2.5 again
+		Popen._internal_poll.im_func.func_defaults = defaults
+except (AttributeError, ValueError):
+	pass
+
+
+def kill(process):
+	try:
+		process.kill()
+	except AttributeError:
+		os.kill(process.pid, SIGKILL)
+
+
+def terminate(process):
+	try:
+		process.terminate()
+	except AttributeError:
+		os.kill(process.pid, SIGTERM)
\ No newline at end of file
--- a/win32.py	Wed Feb 16 15:30:57 2011 +0000
+++ b/win32.py	Wed Feb 23 23:35:27 2011 +0000
@@ -1,19 +1,20 @@
-# Copyright (c) 2011 Chortos-2 <chortos@inbox.lv>
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
 
 from __future__ import division, with_statement
+import sys
 
 try:
 	from compat import *
-	from testcases import (TimeLimitExceeded, MemoryLimitExceeded,
-	                       CanceledByUser, CannotStartTestee)
+	import testcases  # mutual import
 except ImportError:
 	import __main__
 	__main__.import_error(sys.exc_info()[1])
 
-from __main__ import clock
 from ctypes import *
 from ctypes.wintypes import *
+from msvcrt import getch as pause
 from subprocess import Popen
+from __main__ import clock
 
 # Defaults that may be overwritten by values from _subprocess
 INFINITE = -1
@@ -42,7 +43,7 @@
 else:
 	ProcessTimes = namedtuple('ProcessTimes', 'kernel user')
 
-__all__ = 'call', 'kill', 'terminate'
+__all__ = 'call', 'kill', 'terminate', 'pause'
 
 
 # Automatically convert _subprocess handle objects into low-level HANDLEs
@@ -315,7 +316,7 @@
 	            ('PeakProcessMemoryUsed', SIZE_T),
 	            ('PeakJobMemoryUsed', SIZE_T))
 
-prototype = WINFUNCTYPE(BOOL, HANDLE, c_int, c_void_p, DWORD)
+prototype = WINFUNCTYPE(BOOL, Handle, c_int, c_void_p, DWORD)
 flags = (1, 'job'), (1, 'infoclass'), (1, 'info'), (1, 'infosize')
 try:
 	_setjobinfo = prototype(('SetInformationJobObject',windll.kernel32), flags)
@@ -327,7 +328,7 @@
 		if not result: raise WinError()
 	_setjobinfo.errcheck = errcheck
 	def SetInformationJobObject(job, infoclass, info):
-		return _setjobinfo(job, infoclass, info, sizeof(info))
+		return _setjobinfo(job, infoclass, byref(info), sizeof(info))
 
 (
 	JobObjectBasicAccountingInformation,
@@ -417,13 +418,6 @@
 	console_input = True
 	FlushConsoleInputBuffer(stdin)
 
-def kill(process):
-	try:
-		process.terminate()
-	except AttributeError:
-		TerminateProcess(process._handle)
-terminate = kill
-
 def call(*args, **kwargs):
 	case = kwargs.pop('case')
 	job = CreateJobObject(None)
@@ -443,14 +437,14 @@
 	try:
 		case.process = Popen(*args, **kwargs)
 	except OSError:
-		raise CannotStartTestee(sys.exc_info()[1])
+		raise testcases.CannotStartTestee(sys.exc_info()[1])
 	case.time_started = clock()
 	AssignProcessToJobObject(job, case.process._handle)
 	if not console_input:
 		if case.maxwalltime:
 			if (WaitForSingleObject(case.process._handle, case.maxwalltime) !=
 			    WAIT_OBJECT_0):
-				raise TimeLimitExceeded
+				raise testcases.WallTimeLimitExceeded
 		else:
 			case.process.wait()
 	else:
@@ -467,9 +461,9 @@
 						    ir.EventType == 1 and
 						    ir.Event.KeyEvent.bKeyDown and
 						    ir.Event.KeyEvent.wVirtualKeyCode == 27):
-							raise CanceledByUser
+							raise testcases.CanceledByUser
 				else:
-					raise TimeLimitExceeded
+					raise testcases.WallTimeLimitExceeded
 		else:
 			while case.process.poll() is None:
 				if (WaitForMultipleObjects(handles, False, INFINITE) ==
@@ -479,16 +473,24 @@
 					    ir.EventType == 1 and
 					    ir.Event.KeyEvent.bKeyDown and
 					    ir.Event.KeyEvent.wVirtualKeyCode == 27):
-						raise CanceledByUser
+						raise testcases.CanceledByUser
 	case.time_stopped = clock()
-	if case.maxcputime and GetProcessTimes:
+	if GetProcessTimes:
 		try:
 			times = GetProcessTimes(case.process._handle)
 		except WindowsError:
 			pass
 		else:
-			if times.kernel + times.user > case.maxcputime:
-				raise TimeLimitExceeded
+			time = times.kernel + times.user
+			case.time_stopped = time
+			case.time_started = 0
+			case.time_limit_string = case.cpu_time_limit_string
+			if case.maxcputime and time > case.maxcputime:
+				raise testcases.CPUTimeLimitExceeded
+	if case.maxcputime and case.process.returncode == 1816:
+		raise testcases.CPUTimeLimitExceeded
+	if case.maxmemory and case.process.returncode == -0x3ffffffb:
+		raise testcases.MemoryLimitExceeded
 	if case.maxmemory and GetProcessMemoryInfo:
 		try:
 			counters = GetProcessMemoryInfo(case.process._handle)
@@ -496,4 +498,12 @@
 			pass
 		else:
 			if counters.PeakPagefileUsage > case.maxmemory * 1048576:
-				raise MemoryLimitExceeded
\ No newline at end of file
+				raise testcases.MemoryLimitExceeded
+
+
+def kill(process):
+	try:
+		process.terminate()
+	except AttributeError:
+		TerminateProcess(process._handle)
+terminate = kill
\ No newline at end of file