diff 2.00/testcases.py @ 25:b500e117080e

Bug fixes and overhead reduction Added the --problem/-p option. (WARNING: not the same as the -p option of test.py 1.x.) The problem names supplied are not validated. Added zip_longest to compat.py. Experimental: problem names are now _always_ printed for multi-problem sets. Overhead: Escape presses are now checked only once every .15 seconds (at least kbhit() on Windows is very slow). Overhead: sleep(0) is now called in the time-control-and-Escape-watching loop (at least on Windows, it immediately transfers control to some waiting thread). Bug fix: compat.py now overwrites built-ins only while including testconfs (--help was broken in Python 2). Bug fix: ReadDeleting in config.py now closes the file it opens (especially important on Windows, where open files cannot be deleted). Bug fix: added callable to compat.py (it is absent from Python 3). Bug fix: the default (built-in) output validator now properly handles unwanted trailing data. Bug fix: testconfs in custom archives no more raise NameError. Bug fix: if a validator program cannot be launched, CannotStartValidator is now raised instead of the fatal OSError.
author Oleg Oshmyan <chortos@inbox.lv>
date Thu, 23 Sep 2010 23:05:58 +0000
parents c23d81f4a1a3
children 5bbb68833868
line wrap: on
line diff
--- a/2.00/testcases.py	Thu Sep 23 00:11:24 2010 +0000
+++ b/2.00/testcases.py	Thu Sep 23 23:05:58 2010 +0000
@@ -59,9 +59,9 @@
 			atexit.register(cleanup)
 			del cleanup
 			tty.setcbreak(sys.stdin.fileno())
-			def canceled():
-				while select.select((sys.stdin,), (), (), 0)[0]:
-					if sys.stdin.read(1) == '\33':
+			def canceled(select=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():
@@ -70,14 +70,14 @@
 			def pause():
 				sys.stdin.read(1)
 	else:
-		def canceled():
-			while msvcrt.kbhit():
-				c = msvcrt.getch()
+		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
-					msvcrt.getch()
+					getch()
 			return False
 		def init_canceled():
 			while msvcrt.kbhit():
@@ -298,29 +298,11 @@
 			# Compare the output with the reference output
 			case.open_outfile()
 			with case.outfile.open() as refoutput:
-				for line, refline in zip(output, refoutput):
-					if not isinstance(refline, basestring):
+				for line, refline in zip_longest(output, refoutput):
+					if refline is not None and not isinstance(refline, basestring):
 						line = bytes(line, sys.getdefaultencoding())
 					if line != refline:
 						raise WrongAnswer
-				try:
-					try:
-						next(output)
-					except NameError:
-						output.next()
-				except StopIteration:
-					pass
-				else:
-					raise WrongAnswer
-				try:
-					try:
-						next(refoutput)
-					except NameError:
-						refoutput.next()
-				except StopIteration:
-					pass
-				else:
-					raise WrongAnswer
 			return 1
 		elif callable(case.validator):
 			return case.validator(output)
@@ -330,10 +312,12 @@
 			if case.problem.config.ansname:
 				case.open_outfile()
 				case.outfile.copy(case.problem.config.ansname)
-			case.process = Popen(case.validator, stdin=devnull, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=-1)
+			try:
+				case.process = Popen(case.validator, stdin=devnull, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=-1)
+			except OSError:
+				raise CannotStartValidator(sys.exc_info()[1])
 			comment = case.process.communicate()[0].strip()
-			lower = comment.lower()
-			match = re.match(r'(ok|correct|wrong(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', lower)
+			match = re.match(r'(?i)(ok|correct|wrong(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', comment)
 			if match:
 				comment = comment[match.end():]
 			if not case.problem.config.maxexitcode:
@@ -375,11 +359,12 @@
 				# FIXME: 2.5 lacks the delete parameter
 				with tempfile.NamedTemporaryFile(delete=False) as f:
 					inputdatafname = f.name
-				context = CopyDeleting(case, case.infile, inputdatafname)
+				contextmgr = CopyDeleting(case, case.infile, inputdatafname)
 			else:
 				inputdatafname = case.problem.config.inname
-				context = Copying(case.infile, inputdatafname)
-			with context:
+				contextmgr = Copying(case.infile, inputdatafname)
+			with contextmgr:
+				# FIXME: this U doesn't do anything good for the child process, does it?
 				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:
 						# TODO: make sure outfile.file is passed to Popen if needed
@@ -393,14 +378,22 @@
 						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
-								elif canceled():
-									raise CanceledByUser
+								# 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(0)
 						else:
 							time_end = case.time_started + case.maxtime
 							while True:
@@ -410,8 +403,13 @@
 									break
 								elif now >= time_end:
 									raise TimeLimitExceeded
-								elif canceled():
-									raise CanceledByUser
+								else:
+									if now >= time_next_check:
+										if canceled():
+											raise CanceledByUser
+										else:
+											time_next_check = now + .15
+								 	time.sleep(0)
 						if config.globalconf.force_zero_exitcode and case.process.returncode:
 							raise NonZeroExitCode(case.process.returncode)
 						callback()
@@ -430,14 +428,20 @@
 			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
-					elif canceled():
-						raise CanceledByUser
+					else:
+						if now >= time_next_check:
+							if canceled():
+								raise CanceledByUser
+							else:
+								time_next_check = now + .15
+					 	time.sleep(0)
 			else:
 				time_end = case.time_started + case.maxtime
 				while True:
@@ -447,8 +451,13 @@
 						break
 					elif now >= time_end:
 						raise TimeLimitExceeded
-					elif canceled():
-						raise CanceledByUser
+					else:
+						if now >= time_next_check:
+							if canceled():
+								raise CanceledByUser
+							else:
+								time_next_check = now + .15
+					 	time.sleep(0)
 			if config.globalconf.force_zero_exitcode and case.process.returncode:
 				raise NonZeroExitCode(case.process.returncode)
 			callback()