changeset 90:1fb319ec33af

Skimming mode added (-k/--skim option) In skimming mode, as soon as a single test case within a test group is failed, the remaining test cases in the same group are skipped. Bug fix and simply a bit of refactoring: TestCase.has_iofiles and TestCase.has_ansfile are now defined (the meaning should be clear from the names).
author Oleg Oshmyan <chortos@inbox.lv>
date Mon, 28 Feb 2011 15:32:22 +0000
parents 3ae6cb69e4ef
children c62c9bfd614a
files problem.py testcases.py upreckon-vcs
diffstat 3 files changed, 40 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/problem.py	Mon Feb 28 15:10:40 2011 +0000
+++ b/problem.py	Mon Feb 28 15:32:22 2011 +0000
@@ -46,7 +46,7 @@
 		self.__dict__ = mydict
 
 class TestContext(object):
-	pass
+	__slots__ = ()
 
 test_context_end = object()
 
@@ -138,6 +138,8 @@
 				sys.stdout.flush()
 				try:
 					granted = case(lambda: (say('%7.3f%s s, ' % (case.time_stopped - case.time_started, case.time_limit_string), end=''), sys.stdout.flush()))
+				except testcases.TestCaseSkipped:
+					verdict = 'skipped due to skimming mode'
 				except testcases.CanceledByUser:
 					verdict = 'canceled by the user'
 				except testcases.WallTimeLimitExceeded:
@@ -187,6 +189,7 @@
 							comment = ' (%s)' % comment
 					if granted >= 1:
 						contexts[-1].case_correct()
+						prob.testcases.send(True)
 						verdict = 'OK' + comment
 					elif not granted:
 						verdict = 'wrong answer' + comment
@@ -206,8 +209,7 @@
 			sys.stdout.flush()
 			return weighted, prob.config.taskweight
 		finally:
-			if options.erase and (not prob.config.stdio or case and
-				(case.validator and not callable(case.validator))):
+			if options.erase and case and case.has_iofiles:
 				for var in 'in', 'out':
 					name = getattr(prob.config, var + 'name')
 					if name:
@@ -215,7 +217,7 @@
 							os.remove(name)
 						except Exception:
 							pass
-				if case.validator and not callable(case.validator):
+				if case.has_ansfile:
 					if prob.config.ansname:
 						try:
 							os.remove(prob.config.ansname)
--- a/testcases.py	Mon Feb 28 15:10:40 2011 +0000
+++ b/testcases.py	Mon Feb 28 15:32:22 2011 +0000
@@ -52,6 +52,7 @@
 # Exceptions
 
 class TestCaseNotPassed(Exception): __slots__ = ()
+class TestCaseSkipped(TestCaseNotPassed): __slots__ = ()
 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = ()
 class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
 class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
@@ -160,7 +161,8 @@
 			case.realoutname = case.problem.config.dummyoutname
 	
 	@abstractmethod
-	def test(case): raise NotImplementedError
+	def test(case):
+		raise NotImplementedError
 	
 	def __call__(case, callback):
 		case.has_called_back = False
@@ -178,6 +180,14 @@
 				callback()
 			case.cleanup()
 	
+	@property
+	def has_iofiles(case):
+		return False
+	
+	@property
+	def has_ansfile(case):
+		return False
+	
 	def cleanup(case):
 		#if getattr(case, 'infile', None):
 		#	case.infile.close()
@@ -229,6 +239,13 @@
 			raise CannotReadAnswerFile(e)
 
 
+class SkippedTestCase(TestCase):
+	__slots__ = ()
+	
+	def test(case, callback):
+		raise TestCaseSkipped
+
+
 class ValidatedTestCase(TestCase):
 	__slots__ = 'validator'
 	
@@ -278,9 +295,17 @@
 class BatchTestCase(ValidatedTestCase):
 	__slots__ = ()
 	
+	@property
+	def has_iofiles(case):
+		return (not case.problem.config.stdio or
+		        case.validator and not callable(case.validator))
+	
+	@property
+	def has_ansfile(case):
+		return case.validator and not callable(case.validator)
+	
 	def test(case, callback):
 		case.open_infile()
-		case.time_started = 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
@@ -414,9 +439,14 @@
 			if not group:
 				continue
 			yield problem.TestGroup(prob.config.groupweight.get(k, prob.config.groupweight.get(None)))
+			case_type = _types[prob.config.kind]
 			for j, i in enumerate(group):
 				s = str(i).zfill(prob.config.padtests)
-				yield _types[prob.config.kind](prob, s, False, getpoints(i, j, k))
+				if not (yield case_type(prob, s, False, getpoints(i, j, k))):
+					if options.skim:
+						case_type = SkippedTestCase
+				else:
+					yield
 			yield problem.test_context_end
 	else:
 		for i in prob.config.tests:
--- a/upreckon-vcs	Mon Feb 28 15:10:40 2011 +0000
+++ b/upreckon-vcs	Mon Feb 28 15:32:22 2011 +0000
@@ -19,6 +19,7 @@
 parser.add_option('-x', '--auto-exit', dest='pause', action='store_false', default=True, help='do not wait for a key to be pressed after finishing testing')
 parser.add_option('-s', '--save-io', dest='erase', action='store_false', default=True, help='do not delete the copies of input/output files after the last test case; create copies of input files and store output in files even if the solution uses standard I/O; delete the stored input/output files if the solution uses standard I/O and the -c/--cleanup option is specified')
 parser.add_option('-t', '--detect-time', dest='autotime', action='store_true', default=False, help='spend a second detecting the most precise time measurement function')
+parser.add_option('-k', '--skim', action='store_true', default=False, help='skip test groups as soon as one test case is failed')
 parser.add_option('--no-time-limits', dest='no_maxtime', action='store_true', default=False, help='disable all time limits')
 
 options, args = parser.parse_args()