changeset 227:1b775632cbd9

Third attempt to work around unwarranted ERROR_ACCESS_DENIED on Windows
author Oleg Oshmyan <chortos@inbox.lv>
date Wed, 06 Jun 2012 20:22:18 +0100
parents 2cdd966bc5fb (diff) ebb35960b5bc (current diff)
children 928a6091454c
files
diffstat 9 files changed, 90 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Wed Jun 06 20:22:18 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-ad4362bf98589c4be765ff91b4d631a18347c4ed 2.03.0
-fbdea5b8cc684e67fc96030eb7f429dcae93bd53 2.03.1
--- a/setup-exe.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/setup-exe.py	Wed Jun 06 20:22:18 2012 +0100
@@ -30,7 +30,7 @@
 os.rename('upreckon/unix.py', 'upreckon/unix.py~')
 try:
 	setup(name='Upreckon',
-	      version='2.03.1',
+	      version='2.04.0dev',
 	      author='Oleg Oshmyan',
 	      author_email='chortos@inbox.lv',
 	      url='http://chortos.selfip.net/~astiob/upreckon/',
--- a/setup.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/setup.py	Wed Jun 06 20:22:18 2012 +0100
@@ -28,7 +28,7 @@
 	ext_modules = []
 
 setup(name='upreckon',
-      version='2.03.1',
+      version='2.04.0dev',
       author='Oleg Oshmyan',
       author_email='chortos@inbox.lv',
       url='http://chortos.selfip.net/~astiob/upreckon/',
--- a/upreckon/_unixmodule.cpp	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/_unixmodule.cpp	Wed Jun 06 20:22:18 2012 +0100
@@ -2,7 +2,6 @@
 
 #include <Python.h>
 #include <structmember.h>
-#include <stdio.h>
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -155,6 +154,9 @@
 #ifdef HAVE_TERMIOS_H
 static bool catch_escape = false;
 static struct termios orig_termios;
+#ifdef O_ASYNC
+static int orig_stdout_fl;
+#endif
 #endif
 
 typedef struct
@@ -405,6 +407,10 @@
 #endif
 #endif
 		
+#if defined HAVE_TERMIOS_H && defined O_ASYNC
+		signal(SIGIO, SIG_DFL);
+#endif
+		
 		if (c2ppipe[1] < 3)
 		{
 			int newfd;
@@ -1275,6 +1281,9 @@
 #ifdef HAVE_TERMIOS_H
 	if (catch_escape)
 	{
+#ifdef O_ASYNC
+		signal(SIGIO, SIG_DFL);
+#endif
 		char c;
 		while (read(0, &c, 1) == -1 && errno == EINTR)
 		{
@@ -1309,6 +1318,9 @@
 static void restore_termios(void)
 {
 	tcsetattr(0, TCSAFLUSH, &orig_termios);
+#ifdef O_ASYNC
+	fcntl(0, F_SETFL, orig_stdout_fl);
+#endif
 #ifdef USE_WAKEUP_FD
 	close_intpipe();
 #endif
@@ -1443,6 +1455,10 @@
 		if (!Py_AtExit(restore_termios) && !tcsetattr(0, TCSAFLUSH, &new_termios))
 		{
 			catch_escape = true;
+#ifdef O_ASYNC
+			orig_stdout_fl = fcntl(0, F_GETFL);
+			fcntl(0, F_SETFL, orig_stdout_fl | O_ASYNC);
+#endif
 		}
 	}
 #ifdef USE_WAKEUP_FD
--- a/upreckon/exceptions.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/exceptions.py	Wed Jun 06 20:22:18 2012 +0100
@@ -13,7 +13,7 @@
 class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
 class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = ()
-class CanceledByUser(TestCaseNotPassed): __slots__ = ()
+class CanceledByUser(BaseException): __slots__ = ()
 
 class WrongAnswer(TestCaseNotPassed):
 	__slots__ = 'comment'
--- a/upreckon/files.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/files.py	Wed Jun 06 20:22:18 2012 +0100
@@ -230,7 +230,7 @@
 		intpath = self._internal_path.split('/') if self._internal_path else ()
 		return os.path.join(self._external_path,
 		                    *(filename.replace('.', os.path.extsep)
-	                          for filename in intpath))
+		                      for filename in intpath))
 	
 	def exists(self):
 		if self.archive:
--- a/upreckon/testcases.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/testcases.py	Wed Jun 06 20:22:18 2012 +0100
@@ -19,6 +19,8 @@
 	def __enter__(self): pass
 	def __exit__(self, exc_type, exc_value, traceback): pass
 signal_ignorer = DummySignalIgnorer()
+def install_escape_handler(): pass
+def remove_escape_handler(): pass
 
 try:
 	from .win32 import *
@@ -113,7 +115,7 @@
 			case.realoutname = case.problem.config.dummyoutname
 	
 	@abstractmethod
-	def test(case):
+	def test(case, callback):
 		raise NotImplementedError
 	
 	def __call__(case, callback):
@@ -121,8 +123,10 @@
 		case.files_to_delete = []
 		case.time_limit_string = case.wall_time_limit_string
 		try:
+			install_escape_handler()
 			return case.test(callback)
 		finally:
+			remove_escape_handler()
 			now = clock()
 			if getattr(case, 'time_started', None) is None:
 				case.time_started = case.time_stopped = now
--- a/upreckon/unix.py	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/unix.py	Wed Jun 06 20:22:18 2012 +0100
@@ -8,10 +8,13 @@
 from subprocess import Popen
 import os, sys, time
 
-if sys.platform.startswith('java'):
-	from time import clock
-else:
-	from time import time as clock
+try:
+	from time import steady as clock
+except ImportError:
+	if sys.platform.startswith('java'):
+		from time import clock
+	else:
+		from time import time as clock
 
 try:
 	from signal import SIGTERM, SIGKILL
@@ -29,6 +32,10 @@
 	from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL
 	from os import O_NONBLOCK
 	try:
+		from os import O_CLOEXEC, pipe2
+	except ImportError:
+		from os import pipe
+	try:
 		import cPickle as pickle
 	except ImportError:
 		import pickle
@@ -61,11 +68,6 @@
 					time.sleep(.001)
 else:
 	try:
-		from fcntl import FD_CLOEXEC
-	except ImportError:
-		FD_CLOEXEC = 1
-	
-	try:
 		from signal import siginterrupt
 	except ImportError:
 		# Sucks.
@@ -90,12 +92,27 @@
 	except ImportError:
 		setrlimit = None
 	
+	try:
+		pipe2
+	except NameError:
+		try:
+			from fcntl import FD_CLOEXEC
+		except ImportError:
+			FD_CLOEXEC = 1
+		# Pick an arbitrary unique value for O_CLOEXEC
+		O_CLOEXEC = 1 if O_NONBLOCK != 1 else 2
+		def pipe2(flags):
+			r, w = pipe()
+			if flags & O_NONBLOCK:
+				fcntl(r, F_SETFL, fcntl(r, F_GETFL) | O_NONBLOCK)
+				fcntl(w, F_SETFL, fcntl(w, F_GETFL) | O_NONBLOCK)
+			if flags & O_CLOEXEC:
+				fcntl(r, F_SETFD, fcntl(r, F_GETFD) | FD_CLOEXEC)
+				fcntl(w, F_SETFD, fcntl(w, F_GETFD) | FD_CLOEXEC)
+			return r, w
+	
 	# Make SIGCHLD interrupt sleep() and select()
-	sigchld_pipe_read, sigchld_pipe_write = os.pipe()
-	fcntl(sigchld_pipe_read, F_SETFL,
-	      fcntl(sigchld_pipe_read, F_GETFL) | O_NONBLOCK)
-	fcntl(sigchld_pipe_write, F_SETFL,
-	      fcntl(sigchld_pipe_write, F_GETFL) | O_NONBLOCK)
+	sigchld_pipe_read, sigchld_pipe_write = pipe2(O_NONBLOCK)
 	def bury_child(signum, frame):
 		try:
 			bury_child.case.time_stopped = clock()
@@ -115,8 +132,7 @@
 	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)
+		read, write = pipe2(O_CLOEXEC)
 		def preexec_fn():
 			os.close(read)
 			if setrlimit and case.maxmemory:
@@ -308,3 +324,21 @@
 			tty.setcbreak(sys.stdin.fileno())
 			def pause():
 				sys.stdin.read(1)
+else:
+	try:
+		from signal import signal, SIGIO, SIG_DFL
+		from select import select
+	except ImportError:
+		pass
+	else:
+		def sigio_handler(signum, frame):
+			if select((sys.stdin,), (), (), 0)[0]:
+				if os.read(sys.stdin.fileno(), 1) == '\33'.encode('ascii'):
+					remove_escape_handler()
+					raise CanceledByUser
+		def install_escape_handler():
+			signal(SIGIO, sigio_handler)
+			sigio_handler(SIGIO, None)
+		def remove_escape_handler():
+			signal(SIGIO, SIG_DFL)
+		__all__ += 'install_escape_handler', 'remove_escape_handler'
--- a/upreckon/upreckon-vcs	Wed Jun 06 20:22:18 2012 +0100
+++ b/upreckon/upreckon-vcs	Wed Jun 06 20:22:18 2012 +0100
@@ -7,12 +7,14 @@
 from upreckon import compat
 from upreckon.compat import *
 
-parser = optparse.OptionParser(version='Upreckon 2.03.1 ($$REV$$)', epilog='Python 2.6 or newer is required.')
+parser = optparse.OptionParser(version='Upreckon 2.04.0 ($$REV$$)', epilog='Python 2.6 or newer is required.')
 parser.add_option('-1', dest='legacy', action='store_true', default=False, help='handle configuration files in a way more compatible with test.py 1.x')
 parser.add_option('-p', '--problem', dest='problems', metavar='PROBLEM', action='append', help='test only the PROBLEM (this option can be specified more than once with different problem names, all of which will be tested)')
 parser.add_option('--list-problems', action='store_true', default=False, help='just list all problem names')
+parser.add_option('-c', '--cleanup', dest='cleanup', action='store_true', default=False, help='delete the copies of input/output files and exit')
+parser.add_option('-m', '--copy-io', dest='copyonly', action='store_true', default=False, help='create a copy of the input/output files of the last test case for manual testing and exit')
 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')
+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('-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')
 
@@ -74,16 +76,24 @@
 	for taskname in options.problems or globalconf.problems:
 		problem = Problem(taskname)
 		
-		if ntasks: say()
+		if ntasks and not (options.cleanup or options.copyonly): say()
 		if shouldprintnames: say(taskname)
 		
-		real, max = problem.test()
+		if options.cleanup:
+			problem.cleanup()
+		elif options.copyonly:
+			problem.copytestdata()
+		else:
+			real, max = problem.test()
 		
 		ntasks += 1
 		nfulltasks += real == max
 		realscore += real
 		maxscore += max
 	
+	if options.cleanup or options.copyonly:
+		sys.exit()
+	
 	if ntasks != 1:
 		say()
 		say('Grand total: %g/%g weighted points; %d/%d problems solved fully' % (realscore, maxscore, nfulltasks, ntasks))