changeset 174:e0b2fbd7ebe0

Improved built-in output validator; added conf. var. binary The built-in output validator now reads blocks rather than lines, which should make it faster. There is also a new configuration variable called binary, which defaults to False and specifies whether the built-in output validator should not try to translate line breaks. Finally, when binary is false, the built-in output validator now translates line breaks even if it the reference output is in a tape archive.
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 18 Jun 2011 02:55:17 +0100
parents b993d9257400
children 35d59ba0e27c
files upreckon/compat.py upreckon/config.py upreckon/files.py upreckon/testcases.py
diffstat 4 files changed, 37 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/upreckon/compat.py	Thu Jun 16 01:24:10 2011 +0100
+++ b/upreckon/compat.py	Sat Jun 18 02:55:17 2011 +0100
@@ -1,6 +1,6 @@
 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
 
-# A compatibility layer for Python 2.5+. This is what lets test.py
+# A compatibility layer for Python 2.5+. This is what lets Upreckon
 # run on all versions of Python starting with 2.5, including Python 3.
 
 # A few notes regarding some compatibility-driven peculiarities
--- a/upreckon/config.py	Thu Jun 16 01:24:10 2011 +0100
+++ b/upreckon/config.py	Sat Jun 18 02:55:17 2011 +0100
@@ -31,6 +31,7 @@
                     'groupweight': {},
                     'pointmap': {},
                     'stdio': False,
+                    'binary': False,
                     'dummyinname': '',
                     'dummyoutname': '',
                     'tester': None,
--- a/upreckon/files.py	Thu Jun 16 01:24:10 2011 +0100
+++ b/upreckon/files.py	Sat Jun 18 02:55:17 2011 +0100
@@ -60,7 +60,6 @@
 			member.name = target
 			self.file.extract(member)
 		
-		# TODO: somehow automagically emulate universal line break support
 		def open(self, name):
 			return self.file.extractfile(name)
 		
@@ -117,7 +116,7 @@
 				self.file.extract(member)
 		
 		def open(self, name):
-			return self.file.open(name, 'rU')
+			return self.file.open(name, 'r')
 		
 		def exists(self, queried_name):
 			if not hasattr(self, '_namelist'):
@@ -232,7 +231,7 @@
 			else:
 				return contextlib.closing(file)
 		else:
-			return open(self.real_path, 'rU')
+			return open(self.real_path, 'rb')
 	
 	def copy(self, target):
 		if self.archive:
--- a/upreckon/testcases.py	Thu Jun 16 01:24:10 2011 +0100
+++ b/upreckon/testcases.py	Sat Jun 18 02:55:17 2011 +0100
@@ -13,7 +13,7 @@
 from subprocess import Popen, PIPE, STDOUT
 
 import os
-devnull = open(os.path.devnull, 'w+')
+devnull = open(os.path.devnull, 'w+b')
 
 class DummySignalIgnorer(object):
 	def __enter__(self): pass
@@ -179,13 +179,38 @@
 	def validate(case, output):
 		if not case.validator:
 			# Compare the output with the reference output
+			buffer = refbuffer = crlfhalf = refcrlfhalf = ''.encode()
+			crlf = '\r\n'.encode('ascii')
 			case.open_outfile()
 			with case.outfile.open() as refoutput:
-				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:
+				while True:
+					data = output.read(4096 - len(buffer))
+					refdata = refoutput.read(4096 - len(refbuffer))
+					if not case.problem.config.binary:
+						data, refdata = crlfhalf + data, refcrlfhalf + refdata
+						size, refsize = len(data), len(refdata)
+						if data and data != crlfhalf and data[-1] == crlf[0]:
+							size -= 1
+							crlfhalf = data[-1:]
+						else:
+							crlfhalf = ''.encode()
+						if refdata and refdata != refcrlfhalf and refdata[-1] == crlf[0]:
+							refsize -= 1
+							refcrlfhalf = refdata[-1:]
+						else:
+							refcrlfhalf = ''.encode()
+						data = data[:size].replace(crlf, crlf[1:])
+						data = data.replace(crlf[:1], crlf[1:])
+						refdata = refdata[:refsize].replace(crlf, crlf[1:])
+						refdata = refdata.replace(crlf[:1], crlf[1:])
+					buffer += data
+					refbuffer += refdata
+					if not (buffer or refbuffer or crlfhalf or refcrlfhalf):
+						break
+					size = min(len(buffer), len(refbuffer))
+					if buffer[:size] != refbuffer[:size]:
 						raise WrongAnswer
+					buffer, refbuffer = buffer[size:], refbuffer[size:]
 			return 1
 		elif callable(case.validator):
 			return case.validator(output)
@@ -238,7 +263,7 @@
 				inputdatafname = case.problem.config.inname
 				contextmgr = Copying(case.infile, inputdatafname)
 			with contextmgr:
-				with tempfile.TemporaryFile('w+') if options.erase and (not case.validator or callable(case.validator)) else open(case.problem.config.outname, 'w+') as outfile:
+				with tempfile.TemporaryFile('w+b') if options.erase and (not case.validator or callable(case.validator)) else open(case.problem.config.outname, 'w+b') as outfile:
 					with open(inputdatafname) as infile:
 						call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull)
 					if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0:
@@ -255,7 +280,7 @@
 			case.has_called_back = True
 			callback()
 			try:
-				output = open(case.problem.config.outname, 'rU')
+				output = open(case.problem.config.outname, 'rb')
 			except IOError:
 				raise CannotReadOutputFile(sys.exc_info()[1])
 			with output as output:
@@ -275,7 +300,7 @@
 		case.has_called_back = True
 		callback()
 		try:
-			output = open(case.problem.config.outname.replace('$', case.id), 'rU')
+			output = open(case.problem.config.outname.replace('$', case.id), 'rb')
 		except IOError:
 			raise CannotReadOutputFile(sys.exc_info()[1])
 		with output as output: