changeset 146:d5b6708c1955

Distutils support, reorganization and cleaning up * Removed command-line options -t and -u. * Reorganized code: o all modules are now in package upreckon; o TestCaseNotPassed and its descendants now live in a separate module exceptions; o load_problem now lives in module problem. * Commented out mentions of command-line option -c in --help. * Added a distutils-based setup.py.
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 28 May 2011 14:24:25 +0100
parents d2c266c8d820
children 37955420fd66
files _unixmodule.cpp compat.py config.py files.py problem.py publish.sh setup.py testcases.py unix.py upreckon-vcs upreckon.cmd upreckon/__init__.py upreckon/_unixmodule.cpp upreckon/compat.py upreckon/config.py upreckon/exceptions.py upreckon/files.py upreckon/problem.py upreckon/publish.sh upreckon/testcases.py upreckon/unix.py upreckon/upreckon-vcs upreckon/upreckon.cmd upreckon/win32.py win32.py
diffstat 24 files changed, 3867 insertions(+), 3871 deletions(-) [+]
line wrap: on
line diff
--- a/_unixmodule.cpp	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1448 +0,0 @@
-// Copyright (c) 2011 Chortos-2 <chortos@inbox.lv>
-
-#include <Python.h>
-#include <structmember.h>
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#ifdef HAVE_FCNTL_H
-#include <fcntl.h>
-#endif
-
-#include <limits.h>
-
-#ifdef HAVE_SIGNAL_H
-#include <signal.h>
-#endif
-
-#ifdef HAVE_SPAWN_H
-#include <spawn.h>
-#ifdef __APPLE__
-#pragma weak_import posix_spawnp
-#endif
-#endif
-
-#ifdef HAVE_SYS_RESOURCE_H
-#include <sys/resource.h>
-#endif
-
-#ifdef HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
-
-#ifdef HAVE_TERMIOS_H
-#include <termios.h>
-#endif
-
-#if !(defined __cplusplus) && !(defined bool)
-#ifdef HAVE_C99_BOOL
-#define bool _Bool
-#else
-#define bool char
-#endif
-#undef true
-#define true 1
-#undef false
-#define false 0
-#endif
-
-// On Python 2.5, SIGINT handling may get delayed until we return to Python
-#if PY_MAJOR_VERSION > 2 || PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6
-#define USE_WAKEUP_FD
-#endif
-
-#if !(defined RLIMIT_AS) && defined RLIMIT_VMEM
-#define RLIMIT_AS RLIMIT_VMEM
-#endif
-
-// Condition stolen from posixmodule.c of Python 2.7.1
-#if defined __USLC__ && defined __SCO_VERSION__  // SCO UDK Compiler
-//#ifdef HAVE_FORK1
-#define fork fork1
-#endif
-
-// Stolen from posixmodule.c of Python 2.7.1
-#ifdef WITH_NEXT_FRAMEWORK
-#include <crt_externs.h>
-static char **environ = NULL;
-#elif !(defined _MSC_VER) && (!(defined __WATCOMC__) || defined __QNX__)
-extern char **environ;
-#endif
-
-#ifndef Py_PYTIME_H
-typedef struct timeval _PyTime_timeval;
-#ifndef GETTIMEOFDAY_NO_TZ
-#define _PyTime_gettimeofday(tvp) gettimeofday((tvp), NULL)
-#else
-#define _PyTime_gettimeofday(tvp) gettimeofday((tvp))
-#endif
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-#define PyInt_AsLong PyLong_AsLong
-#define PyInt_FromLong PyLong_FromLong
-#define PyNumber_Int PyNumber_Long
-#endif
-
-#define TESTEE_SPAWNED 0
-#define TESTEE_SPAWN_FAILED 1
-#define TESTEE_REPORT_STATUS(status) \
-	do \
-	{ \
-		const char c = (status); \
-		write(c2ppipe[1], &c, 1); \
-	} \
-	while (0)
-
-#if !(defined SIGKILL) && defined SIGTERM
-#define SIGKILL SIGTERM
-#endif
-
-#if defined HAVE_KILL && defined SIGKILL
-#ifdef HAVE_WAITPID
-#define TERM_TESTEE \
-	do \
-	{ \
-		kill(-curpid, SIGKILL); \
-		kill(-curpid, SIGCONT); \
-		while (waitpid(curpid, &retstat, 0) != curpid); \
-	} \
-	while (0)
-#else
-#define TERM_TESTEE \
-	do \
-	{ \
-		kill(-curpid, SIGKILL); \
-		kill(-curpid, SIGCONT); \
-		while (wait(&retstat) != curpid); \
-	} \
-	while (0)
-#endif
-#else
-#define TERM_TESTEE
-#endif
-
-#if defined HAVE_KILL && defined SIGINT
-#define PROPAGATE_SIGINT ((void) kill(-curpid, SIGINT))
-#else
-#define PROPAGATE_SIGINT
-#endif
-
-struct child_stats
-{
-	int returncode;
-	_PyTime_timeval walltime;
-#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
-	_PyTime_timeval cputime;
-	Py_ssize_t memory;
-#endif
-};
-
-static pid_t curpid;
-static const struct child_stats zero_stats = { 0 };
-static PyObject *CannotStartTestee, *CanceledByUser, *WallTimeLimitExceeded,
-                *CPUTimeLimitExceeded, *MemoryLimitExceeded;
-static _PyTime_timeval time_end;
-
-#ifdef USE_WAKEUP_FD
-static char dont_care_buffer[512];
-static int intpipe[2] = { 0 };
-#endif
-
-#ifdef HAVE_TERMIOS_H
-static bool catch_escape = false;
-static struct termios orig_termios;
-#endif
-
-typedef struct
-{
-	PyObject_HEAD
-	int returncode;
-} _unix__PopenPlaceholderObject;
-
-static PyMemberDef _PopenPlaceholder_members[] =
-{
-	{ "returncode", T_INT, offsetof(_unix__PopenPlaceholderObject, returncode), READONLY, NULL },
-	{ NULL }
-};
-
-static PyTypeObject _unix__PopenPlaceholderType =
-{
-#if PY_MAJOR_VERSION >= 3
-	PyVarObject_HEAD_INIT(NULL, 0)
-#else
-	PyObject_HEAD_INIT(NULL)
-	0,                                        /*ob_size*/
-#endif
-	"_unix._PopenPlaceholder",                /*tp_name*/
-	sizeof(_unix__PopenPlaceholderObject),    /*tp_basicsize*/
-	0,                                        /*tp_itemsize*/
-	0,                                        /*tp_dealloc*/
-	0,                                        /*tp_print*/
-	0,                                        /*tp_getattr*/
-	0,                                        /*tp_setattr*/
-	0,                                        /*tp_compare*/
-	0,                                        /*tp_repr*/
-	0,                                        /*tp_as_number*/
-	0,                                        /*tp_as_sequence*/
-	0,                                        /*tp_as_mapping*/
-	0,                                        /*tp_hash */
-	0,                                        /*tp_call*/
-	0,                                        /*tp_str*/
-	0,                                        /*tp_getattro*/
-	0,                                        /*tp_setattro*/
-	0,                                        /*tp_as_buffer*/
-	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
-	0,                                        /*tp_doc*/
-	0,                                        /*tp_traverse*/
-	0,                                        /*tp_clear*/
-	0,                                        /*tp_richcompare*/
-	0,                                        /*tp_weaklistoffset*/
-	0,                                        /*tp_iter*/
-	0,                                        /*tp_iternext*/
-	0,                                        /*tp_methods*/
-	_PopenPlaceholder_members,                /*tp_members*/
-};
-
-#ifndef timeradd
-#define timeradd(a, b, res) \
-	do \
-	{ \
-		(res)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
-		(res)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
-		if ((res)->tv_usec >= 1000000) \
-		{ \
-			++(res)->tv_sec; \
-			(res)->tv_usec -= 1000000; \
-		} \
-	} \
-	while (0)
-#endif
-
-#ifndef timersub
-#define timersub(a, b, res) \
-	do \
-	{ \
-		(res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
-		(res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
-		if ((res)->tv_usec < 0) \
-		{ \
-			--(res)->tv_sec; \
-			(res)->tv_usec += 1000000; \
-		} \
-	} \
-	while (0)
-#endif
-
-#ifndef timerclear
-#define timerclear(tvp) ((void) ((tvp)->tv_sec = (tvp)->tv_usec = 0))
-#endif
-
-#ifndef timerisset
-#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
-#endif
-
-#ifndef timercmp
-#define timercmp(a, b, cmp) \
-	(((a)->tv_sec == (b)->tv_sec) \
-		? ((a)->tv_usec cmp (b)->tv_usec) \
-		: ((a)->tv_sec cmp (b)->tv_sec))
-#endif
-
-// Stolen from posixmodule.c of Python 2.7.1
-static void free_string_array(char **array, Py_ssize_t count)
-{
-	Py_ssize_t i;
-	for (i = 0; i < count; ++i)
-		PyMem_Free(array[i]);
-	PyMem_DEL(array);
-}
-
-// Stolen from termios.c of Python 2.7.1
-static int fdconv(PyObject *obj, void *p)
-{
-	int fd = PyObject_AsFileDescriptor(obj);
-	if (fd >= 0)
-	{
-		*((int *) p) = fd;
-		return 1;
-	}
-	return 0;
-}
-
-// Parts stolen from bltinmodule.c, posixmodule.c and termios.c of Python 2.7.1
-static int my_spawn(PyObject *args, PyObject *kwds, int c2ppipe[2], int maxcputime, Py_ssize_t maxmemory)
-{
-	static const char *const kwlist[] = { "stdin", "stdout", "stderr", NULL };
-	static PyObject *dummy_args = NULL;
-	Py_ssize_t i, argc;
-	char **argv;
-	bool own_args = false;
-	int fdin = 0, fdout = 1, fderr = 2;
-	
-	if (dummy_args == NULL)
-	{
-		if (!(dummy_args = PyTuple_New(0)))
-		{
-			return -1;
-		}
-	}
-	
-	if (!PyArg_ParseTuple(args, "O:call", &args))
-	{
-		return -1;
-	}
-	if (!PyArg_ParseTupleAndKeywords(dummy_args, kwds, "|O&O&O&:call", (char **) kwlist, fdconv, &fdin, fdconv, &fdout, fdconv, &fderr))
-	{
-		return -1;
-	}
-	
-#if PY_MAJOR_VERSION >= 3
-	if (PyUnicode_Check(args))
-#else
-	if (PyString_Check(args) || PyUnicode_Check(args))
-#endif
-	{
-		argc = 1;
-		args = PyTuple_Pack(1, args);
-		if (args == NULL)
-		{
-			return -1;
-		}
-		own_args = true;
-	}
-	else if (!PySequence_Check(args))
-	{
-		PyErr_SetString(PyExc_TypeError, "call() argument must be a sequence or string");
-		return -1;
-	}
-	else
-	{
-		argc = PySequence_Size(args);
-		if (argc < 1)
-		{
-			PyErr_SetString(PyExc_TypeError, "call() argument must not be empty");
-			return -1;
-		}
-	}
-	
-	argv = PyMem_NEW(char *, argc + 1);
-	if (argv == NULL)
-	{
-		if (own_args)
-		{
-			Py_DECREF(args);
-		}
-		PyErr_NoMemory();
-		return -1;
-	}
-	
-	for (i = 0; i < argc; ++i)
-	{
-		if (!PyArg_Parse(PySequence_ITEM(args, i), "et", Py_FileSystemDefaultEncoding, &argv[i]))
-		{
-			free_string_array(argv, i);
-			if (own_args)
-			{
-				Py_DECREF(args);
-			}
-			PyErr_SetString(PyExc_TypeError, "call() argument must contain only strings");
-			return -1;
-		}
-	}
-	argv[argc] = NULL;
-	
-	curpid = fork();
-	if (!curpid)
-	{
-		pid_t pid;
-		int spawn_errno, status, fd, fddupped[3];
-		struct child_stats stats;
-		_PyTime_timeval tvstart, tvend;
-#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
-		struct rusage rusage;
-#endif
-#if defined RLIMIT_AS || defined RLIMIT_CPU
-		struct rlimit rlimit;
-#endif
-		
-		/*
-		Assume no errors occur:
-		* POSIX:2008 doesn't even define any errors for setpgrp,
-		  nor does the (probably copied-verbatim-from-FreeBSD) man page
-		  on Mac OS X 10.6;
-		* none of the error conditions POSIX:2008 does define
-		  for setpgid can occur.
-		*/
-#ifdef HAVE_SETPGID
-		setpgid(0, 0);
-#else //if defined HAVE_SETPGRP
-#ifdef SETPGRP_HAVE_ARG
-		setpgrp(0, 0);
-#else
-		setpgrp();
-#endif
-#endif
-		
-#ifdef SIGINT
-		signal(SIGINT, SIG_DFL);
-#endif
-		
-#if PY_MAJOR_VERSION > 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2
-		_Py_RestoreSignals();
-#else
-#ifdef SIGPIPE
-		signal(SIGPIPE, SIG_DFL);
-#endif
-#ifdef SIGXFSZ
-		signal(SIGXFSZ, SIG_DFL);
-#endif
-#ifdef SIGXFZ
-		signal(SIGXFZ, SIG_DFL);
-#endif
-#endif
-		
-		if (c2ppipe[1] < 3)
-		{
-			int newfd;
-#ifdef F_DUPFD_CLOEXEC
-			newfd = fcntl(c2ppipe[1], F_DUPFD_CLOEXEC, 3);
-			if (newfd == -1)
-			{
-				spawn_errno = errno;
-				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
-				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
-				_exit(127);
-			}
-			c2ppipe[1] = newfd;
-#else
-			newfd = fcntl(c2ppipe[1], F_DUPFD, 3);
-			// Other threads should not fork/spawn right now
-			if (newfd == -1
-			 || fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) | FD_CLOEXEC) == -1)
-			{
-				spawn_errno = errno;
-				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
-				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
-				_exit(127);
-			}
-			c2ppipe[1] = newfd;
-#endif
-		}
-		// Yes, this works as intended even if fdin == fdout == fderr == 0
-		// and there are no open file descriptors except 0 and c2ppipe
-		// FIXME: error handling
-		fddupped[0] = dup(fdin);
-		fddupped[1] = dup(fdout);
-		fddupped[2] = dup(fderr);
-		dup2(fddupped[0], 0);
-		dup2(fddupped[1], 1);
-		dup2(fddupped[2], 2);
-		// FIXME: close() may fail with EINTR or EIO; is setting CLOEXEC safer?
-		// Bear in mind we still want to close them in _this_ process
-		for (fd = sysconf(_SC_OPEN_MAX); --fd > c2ppipe[1]; )
-		{
-			close(fd);
-		}
-		while (--fd >= 3)
-		{
-			close(fd);
-		}
-		
-#ifdef RLIMIT_AS
-		if (maxmemory)
-		{
-			rlimit.rlim_cur = rlimit.rlim_max = maxmemory;
-			setrlimit(RLIMIT_AS, &rlimit);
-		}
-#endif
-#ifdef RLIMIT_CPU
-		if (maxcputime)
-		{
-			rlimit.rlim_cur = rlimit.rlim_max = maxcputime;
-			setrlimit(RLIMIT_CPU, &rlimit);
-		}
-#endif
-		
-#ifdef HAVE_SPAWN_H
-#ifdef __APPLE__
-		if (posix_spawnp != NULL)
-		{
-#endif
-			spawn_errno = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ);
-			_PyTime_gettimeofday(&tvstart);
-			
-			if (spawn_errno)
-			{
-				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
-				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
-				_exit(127);
-			}
-#ifdef __APPLE__
-		}
-		else
-#endif
-#endif
-#if !(defined HAVE_SPAWN_H) || defined __APPLE__
-		{
-			pid = fork();
-			if (!pid)
-			{
-				execvp(argv[0], argv);
-				spawn_errno = errno;
-				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
-				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
-				_exit(127);
-			}
-			else if (pid == -1)
-			{
-				spawn_errno = errno;
-				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
-				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
-				_exit(127);
-			}
-			else
-			{
-				_PyTime_gettimeofday(&tvstart);
-			}
-		}
-#endif
-		TESTEE_REPORT_STATUS(TESTEE_SPAWNED);
-		write(c2ppipe[1], &tvstart, sizeof tvstart);
-		
-#ifdef HAVE_WAIT4
-		while (wait4(pid, &status, 0, &rusage) != pid);
-#elif defined HAVE_WAIT3
-		while (wait3(&status, 0, &rusage) != pid);
-#elif defined HAVE_WAITPID
-		while (waitpid(pid, &status, 0) != pid);
-#else
-		while (wait(&status) != pid);
-#endif
-		
-		_PyTime_gettimeofday(&tvend);
-#if defined HAVE_SYS_RESOURCE_H && !(defined HAVE_WAIT4 || defined HAVE_WAIT3)
-		getrusage(RUSAGE_CHILDREN, &rusage);
-#endif
-		
-		stats = zero_stats;
-		
-		if (WIFEXITED(status) && WEXITSTATUS(status) == 127) _exit(127);
-		else if (WIFSIGNALED(status)) stats.returncode = -WTERMSIG(status);
-		else stats.returncode = WEXITSTATUS(status);
-		
-		timersub(&tvend, &tvstart, &stats.walltime);
-#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
-		timeradd(&rusage.ru_utime, &rusage.ru_stime, &stats.cputime);
-#ifdef __APPLE__
-		stats.memory = rusage.ru_maxrss;
-#else
-		stats.memory = rusage.ru_maxrss << 10;
-#endif
-#endif
-		
-		write(c2ppipe[1], &stats, sizeof stats);
-		_exit(0);
-	}
-	else if (curpid == -1)
-	{
-		PyErr_SetFromErrno(PyExc_OSError);
-		free_string_array(argv, argc);
-		if (own_args)
-		{
-			Py_DECREF(args);
-		}
-		return 0;
-	}
-	
-	/*
-	Assume no errors occur if the child is still alive:
-	* the (probably copied-verbatim-from-FreeBSD) man page
-	  on Mac OS X 10.6 doesn't even define any errors for setpgrp;
-	* none of the error conditions POSIX:2008 defines
-	  for setpgid can occur.
-	*/
-#ifdef HAVE_SETPGID
-	setpgid(curpid, 0);
-#elif defined SETPGRP_HAVE_ARG
-	setpgrp(curpid, 0);
-#endif
-	
-	free_string_array(argv, argc);
-	if (own_args)
-	{
-		Py_DECREF(args);
-	}
-	return 1;
-}
-
-static inline bool attr_to_timeval(PyObject *obj, const char *attr, _PyTime_timeval *ptv)
-{
-#ifdef HAVE_LONG_LONG
-	long long i_whole;
-#else
-	long i_whole;
-#endif
-	PyObject *whole, *frac, *million, *usec, *usec_whole;
-	PyObject *member = PyObject_GetAttrString(obj, attr);
-	if (member == NULL)
-	{
-		return false;
-	}
-	if (member == Py_None)
-	{
-		Py_DECREF(member);
-		timerclear(ptv);
-		return true;
-	}
-	whole = PyNumber_Int(member);
-	if (whole == NULL)
-	{
-		Py_DECREF(member);
-		return false;
-	}
-#ifdef HAVE_LONG_LONG
-	i_whole = PyLong_AsLongLong(whole);
-#else
-	i_whole = PyInt_AsLong(whole);
-#endif
-	if (i_whole == -1 && PyErr_Occurred() != NULL)
-	{
-		Py_DECREF(whole);
-		Py_DECREF(member);
-		return false;
-	}
-	// FIXME: detect time_t overflow
-	ptv->tv_sec = i_whole;
-	frac = PyNumber_Subtract(member, whole);
-	Py_DECREF(whole);
-	Py_DECREF(member);
-	if (frac == NULL)
-	{
-		return false;
-	}
-	million = PyInt_FromLong(1000000);
-	if (million == NULL)
-	{
-		Py_DECREF(frac);
-		return false;
-	}
-	usec = PyNumber_InPlaceMultiply(frac, million);
-	Py_DECREF(million);
-	Py_DECREF(frac);
-	if (usec == NULL)
-	{
-		return false;
-	}
-	usec_whole = PyNumber_Int(usec);
-	Py_DECREF(usec);
-	if (usec_whole == NULL)
-	{
-		return false;
-	}
-	// FIXME: a sanity check (0 <= value < 1000000) here wouldn't harm
-	ptv->tv_usec = PyInt_AsLong(usec_whole);
-	Py_DECREF(usec_whole);
-	return ptv->tv_usec != -1 || PyErr_Occurred() == NULL;
-}
-
-#ifdef __cplusplus
-typedef struct { char a[2]; } two_chars;
-static char is_int(char);
-static char is_int(signed char);
-static char is_int(unsigned char);
-static char is_int(short);
-static char is_int(unsigned short);
-static char is_int(int);
-static char is_int(unsigned);
-static char is_int(long);
-static char is_int(unsigned long);
-#ifdef HAVE_LONG_LONG
-static char is_int(long long);
-static char is_int(unsigned long long);
-#endif
-static two_chars is_int(...);
-#endif
-
-static inline bool timeval_to_attr(_PyTime_timeval *ptv, PyObject *obj, const char *attr)
-{
-	PyObject *value;
-#ifdef __cplusplus
-	// If tv_sec has an integral type and !tv_usec, try to create a Python int
-	if (sizeof is_int(ptv->tv_sec) == sizeof(char) && !ptv->tv_usec)
-	{
-		if (ptv->tv_sec <= LONG_MAX)
-		{
-			value = PyInt_FromLong(ptv->tv_sec);
-		}
-		// FIXME: signed/unsigned comparisons ruin everything
-#ifdef HAVE_LONG_LONG
-		else// if (ptv->tv_sec <= ULLONG_MAX)
-		{
-			value = PyLong_FromUnsignedLongLong(ptv->tv_sec);
-		}
-#else
-//		else if (ptv->tv_sec <= ULONG_MAX)
-//		{
-//			value = PyLong_FromUnsignedLong(ptv->tv_sec);
-//		}
-//#endif
-		else
-		{
-			value = PyFloat_FromDouble(ptv->tv_sec);
-		}
-//
-#endif
-//
-	}
-	else
-#endif
-	{
-		// TODO: use decimal.Decimal or fractions.Fraction
-		value = PyFloat_FromDouble(ptv->tv_sec + ptv->tv_usec * 0.000001);
-	}
-	if (value == NULL)
-	{
-		return false;
-	}
-	if (PyObject_SetAttrString(obj, attr, value) == -1)
-	{
-		return false;
-	}
-	Py_DECREF(value);
-	return true;
-}
-
-/*
-TODO/FIXME:
-* Replace timeval->timespec and select->pselect if pselect is available
- (preferably only if pselect is not a wrapper around select).
-* File descriptors might be >= FD_SETSIZE?
-*/
-static PyObject *_unix_call(PyObject *self, PyObject *args, PyObject *kwds)
-{
-	PyObject *testcase = NULL, *obj;
-	_unix__PopenPlaceholderObject *Popen_placeholder;
-	int spawn_errno = 0, spawn_status, s, c2ppipe[2], retstat;
-	struct child_stats stats = zero_stats;
-	_PyTime_timeval maxwalltime, maxcputime, timeout, time_start;
-	Py_ssize_t maxmemory, r;
-	size_t stats_read = 0;
-	fd_set readfds;
-	char c;
-	bool have_maxwalltime;
-	
-	if (kwds != NULL)
-	{
-		testcase = PyDict_GetItemString(kwds, "case");
-	}
-	if (testcase == NULL)
-	{
-		PyErr_SetString(PyExc_TypeError, "call() requires a keyword argument 'case'");
-		return NULL;
-	}
-	Py_INCREF(testcase);
-	PyDict_DelItemString(kwds, "case");
-	
-	if (!attr_to_timeval(testcase, "maxwalltime", &maxwalltime)
-	 || !attr_to_timeval(testcase, "maxcputime", &maxcputime))
-	{
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	
-	obj = PyObject_GetAttrString(testcase, "maxmemory");
-	if (obj == NULL)
-	{
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	if (PyObject_IsTrue(obj))
-	{
-		PyObject *factor, *bytes;
-		factor = PyInt_FromLong(1024 * 1024);
-		if (factor == NULL)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-		bytes = PyNumber_Multiply(obj, factor);
-		Py_DECREF(factor);
-		if (bytes == NULL)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-		maxmemory = PyNumber_AsSsize_t(bytes, PyExc_OverflowError);
-		Py_DECREF(bytes);
-		if (maxmemory == -1 && PyErr_Occurred() != NULL)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-	}
-	else
-	{
-		maxmemory = 0;
-	}
-	Py_DECREF(obj);
-	
-#ifdef HAVE_PIPE2
-	if (pipe2(c2ppipe, O_CLOEXEC))
-	{
-		PyErr_SetFromErrno(PyExc_IOError);
-		Py_DECREF(testcase);
-		return NULL;
-	}
-#else
-	if (pipe(c2ppipe))
-	{
-		PyErr_SetFromErrno(PyExc_IOError);
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	// Does any other thread fork/spawn right now? Please shoot it in the head
-	// (well, if this ends up causing trouble, anyway)
-	if (fcntl(c2ppipe[0], F_SETFD, fcntl(c2ppipe[0], F_GETFD) | FD_CLOEXEC) == -1
-	 || fcntl(c2ppipe[1], F_SETFD, fcntl(c2ppipe[1], F_GETFD) | FD_CLOEXEC) == -1)
-	{
-		PyErr_SetFromErrno(PyExc_IOError);
-		close(c2ppipe[0]);
-		close(c2ppipe[1]);
-		Py_DECREF(testcase);
-		return NULL;
-	}
-#endif
-	
-	spawn_status = my_spawn(args, kwds, c2ppipe, maxcputime.tv_sec + (maxcputime.tv_usec > 0), maxmemory);
-	close(c2ppipe[1]);
-	if (!spawn_status)
-	{
-		PyObject *type, *value, *traceback, *e;
-		close(c2ppipe[0]);
-		Py_DECREF(testcase);
-		PyErr_Fetch(&type, &value, &traceback);
-		PyErr_NormalizeException(&type, &value, &traceback);
-		Py_XDECREF(traceback);
-		Py_DECREF(type);
-		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
-		Py_DECREF(value);
-		PyErr_SetObject(CannotStartTestee, e);
-		Py_DECREF(e);
-		return NULL;
-	}
-	else if (spawn_status < 0)
-	{
-		close(c2ppipe[0]);
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	
-	// FIXME: use select in order not to miss SIGINT
-	while ((r = read(c2ppipe[0], &c, 1)) == -1 && errno == EINTR)
-	{
-		if (PyErr_CheckSignals() == -1)
-		{
-			PROPAGATE_SIGINT;
-			close(c2ppipe[0]);
-			Py_DECREF(testcase);
-			TERM_TESTEE;
-			return NULL;
-		}
-	}
-	if (r == 1)
-	{
-		if (c == TESTEE_SPAWNED)
-		{
-			size_t got = 0;
-			while (got < sizeof time_start)
-			{
-				r = read(c2ppipe[0], got + (char *) &time_start, sizeof time_start - got);
-				if (r > 0)
-				{
-					got += r;
-				}
-				else if (!r)
-				{
-					errno = 0;
-					PyErr_SetFromErrno(PyExc_IOError);
-					goto spawn_failed;
-				}
-				else if (errno == EINTR)
-				{
-					if (PyErr_CheckSignals() == -1)
-					{
-						PROPAGATE_SIGINT;
-						close(c2ppipe[0]);
-						Py_DECREF(testcase);
-						TERM_TESTEE;
-						return NULL;
-					}
-				}
-				else
-				{
-					PyErr_SetFromErrno(PyExc_IOError);
-					goto spawn_failed;
-				}
-			}
-			if (!timeval_to_attr(&time_start, testcase, "time_started"))
-			{
-				close(c2ppipe[0]);
-				Py_DECREF(testcase);
-				TERM_TESTEE;
-				return NULL;
-			}
-		}
-		else // if (c == TESTEE_SPAWN_FAILED)
-		{
-			size_t got = 0;
-			while (got < sizeof spawn_errno)
-			{
-				r = read(c2ppipe[0], got + (char *) &spawn_errno, sizeof spawn_errno - got);
-				if (r > 0)
-				{
-					got += r;
-				}
-				else if (!r)
-				{
-					// Can't get the real error; use zero instead
-					spawn_errno = 0;
-					break;
-				}
-				else if (errno == EINTR)
-				{
-					if (PyErr_CheckSignals() == -1)
-					{
-						PROPAGATE_SIGINT;
-						close(c2ppipe[0]);
-						Py_DECREF(testcase);
-						TERM_TESTEE;
-						return NULL;
-					}
-				}
-				else
-				{
-					PyErr_SetFromErrno(PyExc_IOError);
-					goto spawn_failed;
-				}
-			}
-			errno = spawn_errno;
-			/*
-			if (errno == EACCES || errno == EINVAL || errno == ELOOP
-			 || errno == ENAMETOOLONG || errno == ENOENT || errno == ENOTDIR
-			 || errno == ENOEXEC || errno == ETXTBSY)
-			{
-				PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PySequence_ITEM(args, 0));
-			}
-			else
-			{*/
-				PyErr_SetFromErrno(PyExc_OSError);
-			//}
-			goto spawn_failed;
-		}
-	}
-	else
-	{
-		PyObject *type, *value, *traceback, *e;
-		if (!r) errno = 0;
-		PyErr_SetFromErrno(PyExc_IOError);
-spawn_failed:
-		Py_DECREF(testcase);
-		close(c2ppipe[0]);
-		TERM_TESTEE;
-		PyErr_Fetch(&type, &value, &traceback);
-		PyErr_NormalizeException(&type, &value, &traceback);
-		Py_XDECREF(traceback);
-		Py_DECREF(type);
-		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
-		Py_DECREF(value);
-		PyErr_SetObject(CannotStartTestee, e);
-		Py_DECREF(e);
-		return NULL;
-	}
-	
-	Py_BEGIN_ALLOW_THREADS
-	timeradd(&time_start, &maxwalltime, &time_end);
-	FD_ZERO(&readfds);
-	have_maxwalltime = timerisset(&maxwalltime);
-	/*
-	Implementations may place limitations on the maximum timeout
-	interval supported. All implementations shall support a maximum
-	timeout interval of at least 31 days. If the timeout argument
-	specifies a timeout interval greater than the implementation-
-	defined maximum value, the maximum value shall be used as the
-	actual timeout value.
-		(POSIX:2008)
-	Therefore the loop and the && timercmp(&time_end, &now, <).
-	*/
-	for (;;)
-	{
-		_PyTime_timeval now;
-		int maxfd = c2ppipe[0];
-#ifdef HAVE_TERMIOS_H
-		if (catch_escape) FD_SET(0, &readfds);
-#endif
-#ifdef USE_WAKEUP_FD
-		FD_SET(intpipe[0], &readfds);
-		if (intpipe[0] > maxfd) maxfd = intpipe[0];
-#endif
-		FD_SET(c2ppipe[0], &readfds);
-		
-		if (have_maxwalltime)
-		{
-			_PyTime_gettimeofday(&now);
-			if (timercmp(&time_end, &now, <))
-			{
-				timerclear(&timeout);
-			}
-			else
-			{
-				timersub(&time_end, &now, &timeout);
-			}
-			
-			s = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
-			
-			if (!s && timercmp(&time_end, &now, <))
-			{
-				close(c2ppipe[0]);
-				TERM_TESTEE;
-				Py_BLOCK_THREADS
-				Py_DECREF(testcase);
-				PyErr_SetObject(WallTimeLimitExceeded, NULL);
-				return NULL;
-			}
-		}
-		else
-		{
-			s = select(maxfd + 1, &readfds, NULL, NULL, NULL);
-		}
-		
-		if (s < 0 && errno == EINTR)
-		{
-			Py_BLOCK_THREADS
-			if (PyErr_CheckSignals() == -1)
-			{
-				PROPAGATE_SIGINT;
-				close(c2ppipe[0]);
-				Py_DECREF(testcase);
-				TERM_TESTEE;
-				return NULL;
-			}
-			Py_UNBLOCK_THREADS
-		}
-		else if (s < 0 && errno != EAGAIN)
-		{
-			Py_BLOCK_THREADS
-			PyErr_SetFromErrno(PyExc_IOError);
-			close(c2ppipe[0]);
-			Py_DECREF(testcase);
-			TERM_TESTEE;
-			return NULL;
-		}
-#ifdef USE_WAKEUP_FD
-		else if (s > 0 && FD_ISSET(intpipe[0], &readfds))
-		{
-			// FIXME: is error handling needed?
-			while (read(intpipe[0], dont_care_buffer, sizeof dont_care_buffer) > 0);
-			Py_BLOCK_THREADS
-			if (PyErr_CheckSignals() == -1)
-			{
-				PROPAGATE_SIGINT;
-				close(c2ppipe[0]);
-				Py_DECREF(testcase);
-				TERM_TESTEE;
-				return NULL;
-			}
-			Py_UNBLOCK_THREADS
-		}
-#endif
-#ifdef HAVE_TERMIOS_H
-		else if (s > 0 && !FD_ISSET(c2ppipe[0], &readfds))
-		{
-			// FIXME: is error and EOF handling needed?
-			if ((r = read(0, &c, 1)) == 1)
-			{
-				if (c == '\33')
-				{
-					close(c2ppipe[0]);
-					TERM_TESTEE;
-					Py_BLOCK_THREADS
-					Py_DECREF(testcase);
-					PyErr_SetObject(CanceledByUser, NULL);
-					return NULL;
-				}
-			}
-			else if (r == -1 && errno == EINTR)
-			{
-				if (PyErr_CheckSignals() == -1)
-				{
-					PROPAGATE_SIGINT;
-					close(c2ppipe[0]);
-					Py_DECREF(testcase);
-					TERM_TESTEE;
-					return NULL;
-				}
-			}
-		}
-#endif
-		else if (s > 0)
-		{
-			bool blocked_threads = false;
-			while ((r = read(c2ppipe[0], stats_read + (char *) &stats, sizeof stats - stats_read)) == -1 && errno == EINTR)
-			{
-				Py_BLOCK_THREADS
-				blocked_threads = true;
-				if (PyErr_CheckSignals() == -1)
-				{
-					PROPAGATE_SIGINT;
-					close(c2ppipe[0]);
-					Py_DECREF(testcase);
-					TERM_TESTEE;
-					return NULL;
-				}
-			}
-			if (r > 0)
-			{
-				stats_read += r;
-			}
-			else if (!r)
-			{
-				break;
-			}
-			else
-			{
-				close(c2ppipe[0]);
-				TERM_TESTEE;
-				if (!blocked_threads)
-				{
-					Py_BLOCK_THREADS
-				}
-				Py_DECREF(testcase);
-				PyErr_SetFromErrno(PyExc_IOError);
-				return NULL;
-			}
-			if (blocked_threads)
-			{
-				Py_UNBLOCK_THREADS
-			}
-		}
-	}
-	close(c2ppipe[0]);
-	Py_END_ALLOW_THREADS
-	
-#ifdef HAVE_WAITPID
-	while (waitpid(curpid, &retstat, 0) != curpid)
-#else
-	while (wait(&retstat) != curpid)
-#endif
-	{
-		if (PyErr_CheckSignals() == -1)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-	}
-	
-	if (WIFEXITED(retstat) && WEXITSTATUS(retstat) == 127)
-	{
-		PyObject *type, *value, *traceback, *e;
-		Py_DECREF(testcase);
-		errno = 0;
-		PyErr_SetFromErrno(PyExc_OSError);
-		PyErr_Fetch(&type, &value, &traceback);
-		PyErr_NormalizeException(&type, &value, &traceback);
-		Py_XDECREF(traceback);
-		Py_DECREF(type);
-		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
-		Py_DECREF(value);
-		PyErr_SetObject(CannotStartTestee, e);
-		Py_DECREF(e);
-		return NULL;
-	}
-	else if (!WIFEXITED(retstat) || WEXITSTATUS(retstat))
-	{
-		Py_DECREF(testcase);
-		if (WIFSTOPPED(retstat))
-		{
-			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: stopped by signal %d", WSTOPSIG(retstat));
-		}
-		else if (WIFSIGNALED(retstat))
-		{
-			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: terminated by signal %d", WTERMSIG(retstat));
-		}
-		else if (WIFEXITED(retstat))
-		{
-			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: %d", WEXITSTATUS(retstat));
-		}
-		else
-		{
-			PyErr_SetString(PyExc_EnvironmentError, "unexpected exit status from worker: not exited, signaled or stopped");
-			return NULL;
-		}
-	}
-	
-	if (stats_read != sizeof stats)
-	{
-		Py_DECREF(testcase);
-		PyErr_SetString(PyExc_EnvironmentError, "unexpectedly early end of output from worker");
-		return NULL;
-	}
-	
-	if (timerisset(&maxwalltime) && timercmp(&stats.walltime, &maxwalltime, >))
-	{
-		Py_DECREF(testcase);
-		PyErr_SetObject(WallTimeLimitExceeded, NULL);
-		return NULL;
-	}
-	
-	obj = PyInt_FromLong(0);
-	if (obj == NULL)
-	{
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	if (PyObject_SetAttrString(testcase, "time_started", obj) == -1)
-	{
-		Py_DECREF(testcase);
-		return NULL;
-	}
-	Py_DECREF(obj);
-	
-#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
-	if (timerisset(&maxcputime) || !timerisset(&maxwalltime))
-	{
-		PyObject *cputls;
-		if (!timeval_to_attr(&stats.cputime, testcase, "time_stopped"))
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-		cputls = PyObject_GetAttrString(testcase, "cpu_time_limit_string");
-		if (cputls == NULL)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-		if (PyObject_SetAttrString(testcase, "time_limit_string", cputls) == -1)
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-		Py_DECREF(cputls);
-		if (timerisset(&maxcputime) && timercmp(&stats.cputime, &maxcputime, >))
-		{
-			Py_DECREF(testcase);
-			PyErr_SetObject(CPUTimeLimitExceeded, NULL);
-			return NULL;
-		}
-	}
-	else
-#endif
-	{
-		if (!timeval_to_attr(&stats.walltime, testcase, "time_stopped"))
-		{
-			Py_DECREF(testcase);
-			return NULL;
-		}
-	}
-	
-#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
-	if (maxmemory && stats.memory > maxmemory)
-	{
-		Py_DECREF(testcase);
-		PyErr_SetObject(MemoryLimitExceeded, NULL);
-		return NULL;
-	}
-#endif
-	
-	Popen_placeholder = PyObject_New(_unix__PopenPlaceholderObject, &_unix__PopenPlaceholderType);
-	if (Popen_placeholder == NULL)
-	{
-		return NULL;
-	}
-	Popen_placeholder->returncode = stats.returncode;
-	PyObject_SetAttrString(testcase, "process", (PyObject *) Popen_placeholder);
-	Py_DECREF(Popen_placeholder);
-	Py_DECREF(testcase);
-	Py_RETURN_NONE;
-}
-
-static PyObject *_unix_pause(PyObject *self)
-{
-#ifdef HAVE_TERMIOS_H
-	if (catch_escape)
-	{
-		char c;
-		while (read(0, &c, 1) == -1 && errno == EINTR);
-	}
-#endif
-	Py_RETURN_NONE;
-}
-
-static PyMethodDef _unixMethods[] =
-{
-	{ "call", (PyCFunction) _unix_call, METH_VARARGS | METH_KEYWORDS, "Call a process." },
-	{ "pause", (PyCFunction) _unix_pause, METH_NOARGS, "Block until a key is pressed." },
-	{ NULL }
-};
-
-#ifdef USE_WAKEUP_FD
-static void close_intpipe(void)
-{
-	close(intpipe[0]);
-	close(intpipe[1]);
-	intpipe[0] = intpipe[1] = 0;
-}
-#endif
-
-#ifdef HAVE_TERMIOS_H
-static void restore_termios(void)
-{
-	tcsetattr(0, TCSAFLUSH, &orig_termios);
-#ifdef USE_WAKEUP_FD
-	close_intpipe();
-#endif
-}
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-#define INIT_FAIL return NULL
-static PyModuleDef _unixmodule =
-{
-	PyModuleDef_HEAD_INIT,
-	"_unix",
-	NULL,
-	-1,
-	_unixMethods
-};
-
-PyMODINIT_FUNC PyInit__unix(void)
-#else
-#define INIT_FAIL return
-PyMODINIT_FUNC init_unix(void)
-#endif
-{
-	struct termios new_termios;
-	PyObject *testcases;
-	
-	_unix__PopenPlaceholderType.tp_new = PyType_GenericNew;
-	if (PyType_Ready(&_unix__PopenPlaceholderType) == -1)
-	{
-		INIT_FAIL;
-	}
-	
-	testcases = PyImport_ImportModule("testcases");
-	if (testcases == NULL)
-	{
-		INIT_FAIL;
-	}
-	if ((CannotStartTestee = PyObject_GetAttrString(testcases, "CannotStartTestee")) == NULL
-	 || (CanceledByUser = PyObject_GetAttrString(testcases, "CanceledByUser")) == NULL
-	 || (WallTimeLimitExceeded = PyObject_GetAttrString(testcases, "WallTimeLimitExceeded")) == NULL
-	 || (CPUTimeLimitExceeded = PyObject_GetAttrString(testcases, "CPUTimeLimitExceeded")) == NULL
-	 || (MemoryLimitExceeded = PyObject_GetAttrString(testcases, "MemoryLimitExceeded")) == NULL)
-	{
-		Py_XDECREF(MemoryLimitExceeded);
-		Py_XDECREF(CPUTimeLimitExceeded);
-		Py_XDECREF(WallTimeLimitExceeded);
-		Py_XDECREF(CanceledByUser);
-		Py_XDECREF(CannotStartTestee);
-		Py_DECREF(testcases);
-		INIT_FAIL;
-	}
-	Py_DECREF(testcases);
-	
-#ifdef WITH_NEXT_FRAMEWORK
-	if (environ == NULL)
-	{
-		environ = *_NSGetEnviron();
-	}
-#endif
-	
-#ifdef USE_WAKEUP_FD
-	if (!intpipe[0] || !intpipe[1])
-	{
-#ifdef HAVE_PIPE2
-		if (pipe2(intpipe, O_CLOEXEC | O_NONBLOCK))
-		{
-			PyErr_SetFromErrno(PyExc_IOError);
-			Py_DECREF(MemoryLimitExceeded);
-			Py_DECREF(CPUTimeLimitExceeded);
-			Py_DECREF(WallTimeLimitExceeded);
-			Py_DECREF(CanceledByUser);
-			Py_DECREF(CannotStartTestee);
-			INIT_FAIL;
-		}
-#else
-		if (pipe(intpipe))
-		{
-			PyErr_SetFromErrno(PyExc_IOError);
-			Py_DECREF(MemoryLimitExceeded);
-			Py_DECREF(CPUTimeLimitExceeded);
-			Py_DECREF(WallTimeLimitExceeded);
-			Py_DECREF(CanceledByUser);
-			Py_DECREF(CannotStartTestee);
-			INIT_FAIL;
-		}
-		// Other threads must not fork now
-		if (fcntl(intpipe[0], F_SETFD, fcntl(intpipe[0], F_GETFD) | FD_CLOEXEC) == -1
-		 || fcntl(intpipe[1], F_SETFD, fcntl(intpipe[1], F_GETFD) | FD_CLOEXEC) == -1
-		 || fcntl(intpipe[0], F_SETFL, fcntl(intpipe[0], F_GETFL) | O_NONBLOCK) == -1
-		 || fcntl(intpipe[1], F_SETFL, fcntl(intpipe[1], F_GETFL) | O_NONBLOCK) == -1)
-		{
-			PyErr_SetFromErrno(PyExc_IOError);
-			close(intpipe[0]);
-			close(intpipe[1]);
-			Py_DECREF(MemoryLimitExceeded);
-			Py_DECREF(CPUTimeLimitExceeded);
-			Py_DECREF(WallTimeLimitExceeded);
-			Py_DECREF(CanceledByUser);
-			Py_DECREF(CannotStartTestee);
-			INIT_FAIL;
-		}
-#endif
-	}
-	
-	PySignal_SetWakeupFd(intpipe[1]);
-#endif
-	
-#ifdef HAVE_TERMIOS_H
-	if (!tcgetattr(0, &orig_termios))
-	{
-		new_termios = orig_termios;
-		// Stolen from tty.py of Python 2.7.1
-		new_termios.c_lflag &= ~(ECHO | ICANON);
-		new_termios.c_cc[VMIN] = 1;
-		new_termios.c_cc[VTIME] = 0;
-		if (!Py_AtExit(restore_termios) && !tcsetattr(0, TCSAFLUSH, &new_termios))
-		{
-			catch_escape = true;
-		}
-	}
-#ifdef USE_WAKEUP_FD
-	else
-	{
-		Py_AtExit(close_intpipe);
-	}
-#endif
-#elif defined USE_WAKEUP_FD
-	Py_AtExit(close_intpipe);
-#endif
-	
-#if PY_MAJOR_VERSION >= 3
-	PyObject *module = PyModule_Create(&_unixmodule);
-	if (module == NULL)
-	{
-		Py_DECREF(MemoryLimitExceeded);
-		Py_DECREF(CPUTimeLimitExceeded);
-		Py_DECREF(WallTimeLimitExceeded);
-		Py_DECREF(CanceledByUser);
-		Py_DECREF(CannotStartTestee);
-	}
-	return module;
-#else
-	Py_InitModule("_unix", _unixMethods);
-#endif
-}
--- a/compat.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-# A compatibility layer for Python 2.5+. This is what lets test.py
-# run on all versions of Python starting with 2.5, including Python 3.
-
-# A few notes regarding some compatibility-driven peculiarities
-# in the use of the language that can be seen in all modules:
-#
-# * Except statements never specify target; instead, when needed,
-#   the exception is taken from sys.exc_info(). Blame the incompatible
-#   syntaxes of the except clause in Python 2.5 and Python 3
-#   and the lack of preprocessor macros in Python of any version ;P.
-#
-# * Keyword-only parameters are never used, even for parameters
-#   that should never be given in as arguments. The reason is
-#   the laziness of some Python developers who have failed to finish
-#   implementing them in Python 2 even though they had several years
-#   of time and multiple version releases to sneak them in.
-#
-# * Abstract classes are only implemented for Python 2.6 and 2.7.
-#   ABC's require the abc module and the specification of metaclasses,
-#   but in Python 2.5, the abc module does not exist, while in Python 3,
-#   metaclasses are specified using a syntax totally incompatible
-#   with Python 2 and not usable conditionally via exec() and such
-#   because it is a detail of the syntax of the class statement itself.
-
-# Some code was adapted from Python 2.7.1 and its documentation.
-# This code is clearly marked as such in preceding comments and is
-# covered by copyright as follows:
-#
-# Copyright (c) 2001-2010 Python Software Foundation; all rights reserved.
-#
-# The code is used according to the PSF License Agreement
-# for Python 2.7.1, whose full text is available from your local
-# installation of Python (enter 'license()' in the interactive
-# interpreter) or from the Web at the following URL:
-#
-# http://docs.python.org/2.7.1/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
-
-try:
-	import builtins
-except ImportError:
-	import __builtin__ as builtins
-
-pseudobuiltins = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'next',
-                  'items', 'keys', 'values', 'zip_longest', 'callable', 'ceil')
-__all__ = pseudobuiltins + ('ABCMeta', 'abstractmethod', 'CompatBuiltins')
-
-try:
-	# Python 3
-	exec('say = print')
-except SyntaxError:
-	try:
-		# Python 2.6/2.7
-		# An alternative is exec('from __future__ import print_function; say = print');
-		# if problems arise with the current line, one should try replacing it
-		# with this one with the future import before abandoning the idea altogether
-		say = getattr(builtins, 'print')
-	except Exception:
-		# Python 2.5
-		import sys
-		# This should fully emulate the print function of Python 2.6 in Python 2.3+
-		# The error messages are taken from Python 2.6
-		# The name bindings at the bottom of this file are in effect
-		def saytypeerror(value, name):
-			return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
-		def say(*values, **kwargs):
-			sep  = kwargs.pop('sep' , None)
-			end  = kwargs.pop('end' , None)
-			file = kwargs.pop('file', None)
-			if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
-			if sep  is None: sep  = ' '
-			if end  is None: end  = '\n'
-			if file is None: file = sys.stdout
-			if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
-			if not isinstance(end, basestring): raise saytypeerror(end, 'end')
-			file.write(sep.join(map(str, values)) + end)
-
-try:
-	from os.path import relpath
-except ImportError:
-	# Python 2.5
-	import os.path as _path
-	
-	# Adapted from Python 2.7.1
-	
-	if hasattr(_path, 'splitunc'):
-		def _abspath_split(path):
-			abs = _path.abspath(_path.normpath(path))
-			prefix, rest = _path.splitunc(abs)
-			is_unc = bool(prefix)
-			if not is_unc:
-				prefix, rest = _path.splitdrive(abs)
-			return is_unc, prefix, [x for x in rest.split(_path.sep) if x]
-	else:
-		def _abspath_split(path):
-			prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path)))
-			return False, prefix, [x for x in rest.split(_path.sep) if x]
-	
-	def relpath(path, start=_path.curdir):
-		"""Return a relative version of a path"""
-		
-		if not path:
-			raise ValueError("no path specified")
-		
-		start_is_unc, start_prefix, start_list = _abspath_split(start)
-		path_is_unc, path_prefix, path_list = _abspath_split(path)
-		
-		if path_is_unc ^ start_is_unc:
-			raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
-			                                                   % (path, start))
-		if path_prefix.lower() != start_prefix.lower():
-			if path_is_unc:
-				raise ValueError("path is on UNC root %s, start on UNC root %s"
-				                                 % (path_prefix, start_prefix))
-			else:
-				raise ValueError("path is on drive %s, start on drive %s"
-				                                 % (path_prefix, start_prefix))
-		# Work out how much of the filepath is shared by start and path.
-		i = 0
-		for e1, e2 in zip(start_list, path_list):
-			if e1.lower() != e2.lower():
-				break
-			i += 1
-		
-		rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:]
-		if not rel_list:
-			return _path.curdir
-		return _path.join(*rel_list)
-	
-	_path.relpath = relpath
-
-def import_urllib():
-	try:
-		# Python 3
-		import urllib.request
-		return urllib.request, lambda url: urllib.request.urlopen(url).read().decode('ascii')
-	except ImportError:
-		# Python 2
-		import urllib
-		return urllib, lambda url: urllib.urlopen(url).read()
-
-try:
-	from abc import ABCMeta, abstractmethod
-except ImportError:
-	ABCMeta, abstractmethod = None, lambda x: x
-
-try:
-	basestring = basestring
-except NameError:
-	basestring = str
-
-# xrange is set to support simple testconf.py's written for test.py 1.x
-try:
-	xrange = range = xrange
-except NameError:
-	xrange = range = range
-
-try:
-	callable = callable
-except NameError:
-	from collections import Callable
-	callable = lambda obj: isinstance(obj, Callable)
-
-try:
-	next = next
-except NameError:
-	next = lambda obj: obj.next()
-
-try:
-	from itertools import imap as map
-except ImportError:
-	map = map
-
-try:
-	from itertools import izip as zip
-except ImportError:
-	zip = zip
-
-try:
-	from itertools import ifilter as filter
-except ImportError:
-	filter = filter
-
-try:
-	items = dict.iteritems
-except AttributeError:
-	items = dict.items
-
-try:
-	keys = dict.iterkeys
-except AttributeError:
-	keys = dict.keys
-
-try:
-	values = dict.itervalues
-except AttributeError:
-	values = dict.values
-
-from math import ceil
-if not isinstance(ceil(0), int):
-	def ceil(x):
-		y = int(x)
-		if y < x: y += 1
-		return y
-
-try:
-	# Python 3
-	from itertools import zip_longest
-except ImportError:
-	try:
-		# Python 2.6/2.7
-		from itertools import izip_longest as zip_longest
-	except ImportError:
-		# Python 2.5
-		from itertools import chain, repeat
-		# Adapted from the documentation of itertools.izip_longest
-		def zip_longest(*args, **kwargs):
-			fillvalue = kwargs.get('fillvalue')
-			def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
-				yield counter()
-			fillers = repeat(fillvalue)
-			iters = [chain(it, sentinel(), fillers) for it in args]
-			try:
-				for tup in zip(*iters):
-					yield tup
-			except IndexError:
-				pass
-
-# Automatically import * from this module into testconf.py's
-class CompatBuiltins(object):
-	__slots__ = 'originals'
-	globals = globals()
-	def __enter__(self):
-		self.originals = {}
-		for name in pseudobuiltins:
-			try:
-				self.originals[name] = getattr(builtins, name)
-			except AttributeError:
-				pass
-			setattr(builtins, name, self.globals[name])
-		return self
-	def __exit__(self, exc_type, exc_val, exc_tb):
-		for name in self.originals:
-			setattr(builtins, name, self.originals[name])
\ No newline at end of file
--- a/config.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,212 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-
-from compat import *
-import files
-from __main__ import options
-
-if files.ZipArchive:
-	try:
-		import zipimport
-	except ImportError:
-		zipimport = None
-else:
-	zipimport = None
-
-import imp, os, sys, tempfile
-
-__all__ = 'load_problem', 'load_global', 'globalconf'
-
-defaults_problem = {'kind': 'batch',
-                    'usegroups': False,
-                    'maxcputime': None,
-                    'maxwalltime': None,
-                    'maxmemory': None,
-                    'dummies': (),
-                    'testsexcluded': (),
-                    'padtests': 0,
-                    'paddummies': 0,
-                    'taskweight': 100,
-                    'groupweight': {},
-                    'pointmap': {},
-                    'stdio': False,
-                    'dummyinname': '',
-                    'dummyoutname': '',
-                    'tester': None,
-                    'maxexitcode': 0,
-                    'inname': '',
-                    'ansname': ''}
-defaults_global = {'problems': None,
-                   'force_zero_exitcode': True}
-defaults_noerase = {'inname': '%.in',
-                    'outname': '%.out',
-                    'ansname': '%.ans'}
-patterns = ('inname', 'outname', 'ansname', 'testcaseinname',
-            'testcaseoutname', 'dummyinname', 'dummyoutname')
-
-class Config(object):
-	__slots__ = 'modules', '__dict__'
-	
-	def __init__(self, *modules):
-		self.modules = modules
-	
-	def __getattr__(self, name):
-		for module in self.modules:
-			try:
-				return getattr(module, name)
-			except AttributeError:
-				pass
-		# TODO: provide a message
-		raise AttributeError(name)
-
-# A helper context manager
-class ReadDeleting(object):
-	__slots__ = 'name', 'file'
-	
-	def __init__(self, name):
-		self.name = name
-	
-	def __enter__(self):
-		try:
-			self.file = open(self.name, 'rU')
-			return self.file
-		except:
-			try:
-				self.__exit__(None, None, None)
-			except:
-				pass
-			raise
-	
-	def __exit__(self, exc_type, exc_val, exc_tb):
-		self.file.close()
-		os.remove(self.name)
-
-def load_problem(problem_name):
-	global builtins
-	try:
-		dwb = sys.dont_write_bytecode
-		sys.dont_write_bytecode = True
-	except AttributeError:
-		pass
-	metafile = files.File('/'.join((problem_name, 'testconf.py')), True, 'configuration')
-	module = None
-	with CompatBuiltins() as builtins:
-		if zipimport and isinstance(metafile.archive, files.ZipArchive):
-			try:
-				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
-			except zipimport.ZipImportError:
-				pass
-			else:
-				del sys.modules['testconf']
-		if not module:
-			try:
-				with metafile.open() as f:
-					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
-			# Handle the case when f is not a true file object but imp requires one
-			except ValueError:
-				# FIXME: 2.5 lacks the delete parameter
-				with tempfile.NamedTemporaryFile(delete=False) as f:
-					inputdatafname = f.name
-				metafile.copy(inputdatafname)
-				with ReadDeleting(inputdatafname) as f:
-					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
-			del sys.modules['testconf']
-	module = Config(module, globalconf)
-	if hasattr(module, 'padwithzeroestolength'):
-		if not hasattr(module, 'padtests'):
-			try:
-				module.padtests = module.padwithzeroestolength[0]
-			except TypeError:
-				module.padtests = module.padwithzeroestolength
-		if not hasattr(module, 'paddummies'):
-			try:
-				module.paddummies = module.padwithzeroestolength[1]
-			except TypeError:
-				module.paddummies = module.padwithzeroestolength
-	if (not hasattr(module, 'maxcputime') and
-	    not hasattr(module, 'maxwalltime') and
-	    hasattr(module, 'maxtime')):
-		module.maxcputime = module.maxtime
-	for name in defaults_problem:
-		setattr(module, name, getattr(module, name, defaults_problem[name]))
-	if not module.dummyinname:
-		module.dummyinname = getattr(module, 'testcaseinname', module.dummyinname)
-	if not module.dummyoutname:
-		module.dummyoutname = getattr(module, 'testcaseoutname', module.dummyoutname)
-	if not hasattr(module, 'path'):
-		if hasattr(module, 'name'):
-			module.path = module.name
-		elif sys.platform != 'win32':
-			module.path = os.path.join(os.path.curdir, problem_name)
-		else:
-			module.path = problem_name
-	for name in 'pointmap', 'groupweight':
-		oldmap = getattr(module, name)
-		if isinstance(oldmap, dict):
-			newmap = {}
-			for key in oldmap:
-				if not options.legacy and isinstance(key, basestring):
-					newmap[key] = oldmap[key]
-				else:
-					try:
-						for k in key:
-							newmap[k] = oldmap[key]
-					except TypeError:
-						newmap[key] = oldmap[key]
-			setattr(module, name, newmap)
-	if options.no_maxtime:
-		module.maxcputime = module.maxwalltime = 0
-	try:
-		sys.dont_write_bytecode = dwb
-	except NameError:
-		pass
-	for name in patterns:
-		if hasattr(module, name):
-			setattr(module, name, getattr(module, name).replace('%', problem_name))
-	return module
-
-def load_global():
-	global builtins
-	try:
-		dwb = sys.dont_write_bytecode
-		sys.dont_write_bytecode = True
-	except AttributeError:
-		pass
-	metafile = files.File('testconf.py', True, 'configuration')
-	module = None
-	with CompatBuiltins() as builtins:
-		if zipimport and isinstance(metafile.archive, files.ZipArchive):
-			try:
-				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
-			except zipimport.ZipImportError:
-				pass
-			else:
-				del sys.modules['testconf']
-		if not module:
-			try:
-				with metafile.open() as f:
-					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
-			# Handle the case when f is not a true file object but imp requires one
-			except ValueError:
-				# FIXME: 2.5 lacks the delete parameter
-				with tempfile.NamedTemporaryFile(delete=False) as f:
-					inputdatafname = f.name
-				metafile.copy(inputdatafname)
-				with ReadDeleting(inputdatafname) as f:
-					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
-			del sys.modules['testconf']
-	for name in defaults_global:
-		setattr(module, name, getattr(module, name, defaults_global[name]))
-	if not options.erase:
-		for name in defaults_noerase:
-			setattr(module, name, getattr(module, name, defaults_noerase[name]))
-	if hasattr(module, 'tasknames'):
-		module.problems = module.tasknames
-	global globalconf
-	globalconf = module
-	try:
-		sys.dont_write_bytecode = dwb
-	except NameError:
-		pass
-	return module
\ No newline at end of file
--- a/files.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>
-
-"""File access routines and classes with support for archives."""
-
-from __future__ import division, with_statement
-
-from compat import *
-import contextlib, os, shutil, sys
-
-# You don't need to know about anything else.
-__all__ = 'File',
-
-# In these two variables, use full stops no matter what os.extsep is;
-# all full stops will be converted to os.extsep on the fly
-archives = 'tests.tar', 'tests.zip', 'tests.tgz', 'tests.tar.gz', 'tests.tbz2', 'tests.tar.bz2'
-formats = {}
-
-class Archive(object):
-	__slots__ = 'file'
-	
-	if ABCMeta:
-		__metaclass__ = ABCMeta
-	
-	def __new__(cls, path):
-		"""
-		Create a new instance of the archive class corresponding
-		to the file name in the given path.
-		"""
-		if cls is not Archive:
-			return object.__new__(cls)
-		else:
-			# Do this by hand rather than through os.path.splitext
-			# because we support multi-dotted file name extensions
-			ext = path.partition(os.path.extsep)[2]
-			while ext:
-				if ext in formats:
-					return formats[ext](path)
-				ext = ext.partition(os.path.extsep)[2]
-			raise LookupError("unsupported archive file name extension in file name '%s'" % filename)
-	
-	@abstractmethod
-	def __init__(self, path): raise NotImplementedError
-	
-	@abstractmethod
-	def extract(self, name, target): raise NotImplementedError
-
-try:
-	import tarfile
-except ImportError:
-	TarArchive = None
-else:
-	class TarArchive(Archive):
-		__slots__ = '_namelist'
-		
-		def __init__(self, path):
-			self.file = tarfile.open(path)
-		
-		def extract(self, name, target):
-			member = self.file.getmember(name)
-			member.name = target
-			self.file.extract(member)
-		
-		# TODO: somehow automagically emulate universal line break support
-		def open(self, name):
-			return self.file.extractfile(name)
-		
-		def exists(self, queried_name):
-			if not hasattr(self, '_namelist'):
-				names = set()
-				for name in self.file.getnames():
-					cutname = name
-					while cutname:
-						names.add(cutname)
-						cutname = cutname.rpartition('/')[0]
-				self._namelist = frozenset(names)
-			return queried_name in self._namelist
-		
-		def __enter__(self):
-			if hasattr(self.file, '__enter__'):
-				self.file.__enter__()
-			return self
-		
-		def __exit__(self, exc_type, exc_value, traceback):
-			if hasattr(self.file, '__exit__'):
-				return self.file.__exit__(exc_type, exc_value, traceback)
-			elif exc_type is None:
-				self.file.close()
-			else:
-				# This code was shamelessly copied from tarfile.py of Python 2.7
-				if not self.file._extfileobj:
-					self.file.fileobj.close()
-				self.file.closed = True
-	
-	formats['tar'] = formats['tgz'] = formats['tar.gz'] = formats['tbz2'] = formats['tar.bz2'] = TarArchive
-
-try:
-	import zipfile
-except ImportError:
-	ZipArchive = None
-else:
-	class ZipArchive(Archive):
-		__slots__ = '_namelist'
-		
-		def __init__(self, path):
-			self.file = zipfile.ZipFile(path)
-		
-		def extract(self, name, target):
-			member = self.file.getinfo(name)
-			# FIXME: 2.5 lacks ZipFile.extract
-			if os.path.isabs(target):
-				# To my knowledge, this is as portable as it gets
-				path = os.path.join(os.path.splitdrive(target)[0], os.path.sep)
-				member.filename = os.path.relpath(target, path)
-				self.file.extract(member, path)
-			else:
-				member.filename = os.path.relpath(target)
-				self.file.extract(member)
-		
-		def open(self, name):
-			return self.file.open(name, 'rU')
-		
-		def exists(self, queried_name):
-			if not hasattr(self, '_namelist'):
-				names = set()
-				for name in self.file.namelist():
-					cutname = name
-					while cutname:
-						names.add(cutname)
-						cutname = cutname.rpartition('/')[0]
-				self._namelist = frozenset(names)
-			return queried_name in self._namelist
-		
-		def __enter__(self):
-			if hasattr(self.file, '__enter__'):
-				self.file.__enter__()
-			return self
-		
-		def __exit__(self, exc_type, exc_value, traceback):
-			if hasattr(self.file, '__exit__'):
-				return self.file.__exit__(exc_type, exc_value, traceback)
-			else:
-				return self.file.close()
-	
-	formats['zip'] = ZipArchive
-
-# Remove unsupported archive formats and replace full stops
-# with the platform-dependent file name extension separator
-def issupported(filename, formats=formats):
-	ext = filename.partition('.')[2]
-	while ext:
-		if ext in formats: return True
-		ext = ext.partition('.')[2]
-	return False
-archives = [filename.replace('.', os.path.extsep) for filename in filter(issupported, archives)]
-formats = dict((item[0].replace('.', os.path.extsep), item[1]) for item in items(formats))
-
-open_archives = {}
-
-def open_archive(path):
-	if path in open_archives:
-		return open_archives[path]
-	else:
-		open_archives[path] = archive = Archive(path)
-		return archive
-
-class File(object):
-	__slots__ = 'virtual_path', 'real_path', 'full_real_path', 'archive'
-	
-	def __init__(self, virtpath, allow_root=False, msg='test data'):
-		self.virtual_path = virtpath
-		self.archive = None
-		if not self.realize_path('', tuple(comp.replace('.', os.path.extsep) for comp in virtpath.split('/')), allow_root):
-			raise IOError("%s file '%s' could not be found" % (msg, virtpath))
-	
-	def realize_path(self, root, virtpath, allow_root=False, hastests=False):
-		if root and not os.path.exists(root):
-			return False
-		if len(virtpath) > 1:
-			if self.realize_path(os.path.join(root, virtpath[0]), virtpath[1:], allow_root, hastests):
-				return True
-			elif not hastests:
-				if self.realize_path(os.path.join(root, 'tests'), virtpath, allow_root, True):
-					return True
-				for archive in archives:
-					path = os.path.join(root, archive)
-					if os.path.exists(path):
-						if self.realize_path_archive(open_archive(path), '', virtpath, path):
-							return True
-			if self.realize_path(root, virtpath[1:], allow_root, hastests):
-				return True
-		else:
-			if not hastests:
-				path = os.path.join(root, 'tests', virtpath[0])
-				if os.path.exists(path):
-					self.full_real_path = self.real_path = path
-					return True
-				for archive in archives:
-					path = os.path.join(root, archive)
-					if os.path.exists(path):
-						if self.realize_path_archive(open_archive(path), '', virtpath, path):
-							return True
-			if hastests or allow_root:
-				path = os.path.join(root, virtpath[0])
-				if os.path.exists(path):
-					self.full_real_path = self.real_path = path
-					return True
-		return False
-	
-	def realize_path_archive(self, archive, root, virtpath, archpath):
-		if root and not archive.exists(root):
-			return False
-		if root: path = ''.join((root, '/', virtpath[0]))
-		else: path = virtpath[0]
-		if len(virtpath) > 1:
-			if self.realize_path_archive(archive, path, virtpath[1:], archpath):
-				return True
-			elif self.realize_path_archive(archive, root, virtpath[1:], archpath):
-				return True
-		else:
-			if archive.exists(path):
-				self.archive = archive
-				self.real_path = path
-				self.full_real_path = os.path.join(archpath, *path.split('/'))
-				return True
-		return False
-	
-	def open(self):
-		if self.archive:
-			file = self.archive.open(self.real_path)
-			if hasattr(file, '__exit__'):
-				return file
-			else:
-				return contextlib.closing(file)
-		else:
-			return open(self.real_path, 'rU')
-	
-	def copy(self, target):
-		if self.archive:
-			self.archive.extract(self.real_path, target)
-		else:
-			shutil.copy(self.real_path, target)
\ No newline at end of file
--- a/problem.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-
-from compat import *
-import config, testcases
-from __main__ import options
-
-import os, re, sys
-
-try:
-	from collections import deque
-except ImportError:
-	deque = list
-
-try:
-	import signal
-except ImportError:
-	signalnames = ()
-else:
-	# Construct a cache of all signal names available on the current
-	# platform. Prefer names from the UNIX standards over other versions.
-	unixnames = frozenset(('HUP', 'INT', 'QUIT', 'ILL', 'ABRT', 'FPE', 'KILL', 'SEGV', 'PIPE', 'ALRM', 'TERM', 'USR1', 'USR2', 'CHLD', 'CONT', 'STOP', 'TSTP', 'TTIN', 'TTOU', 'BUS', 'POLL', 'PROF', 'SYS', 'TRAP', 'URG', 'VTALRM', 'XCPU', 'XFSZ'))
-	signalnames = {}
-	for name in dir(signal):
-		if re.match('SIG[A-Z]+$', name):
-			value = signal.__dict__[name]
-			if isinstance(value, int) and (value not in signalnames or name[3:] in unixnames):
-				signalnames[value] = name
-	del unixnames
-
-__all__ = 'Problem', 'TestContext', 'test_context_end', 'TestGroup'
-
-def strerror(e):
-	s = getattr(e, 'strerror', None)
-	if not s: s = str(e)
-	return ' (%s%s)' % (s[0].lower(), s[1:]) if s else ''
-
-class Cache(object):
-	def __init__(self, mydict):
-		self.__dict__ = mydict
-
-class TestContext(object):
-	__slots__ = ()
-
-test_context_end = object()
-
-class TestGroup(TestContext):
-	__slots__ = 'points', 'case', 'log', 'correct', 'allcorrect', 'real', 'max', 'ntotal', 'nvalued', 'ncorrect', 'ncorrectvalued'
-	
-	def __init__(self, points=None):
-		self.points = points
-		self.real = self.max = self.ntotal = self.nvalued = self.ncorrect = self.ncorrectvalued = 0
-		self.allcorrect = True
-		self.log = []
-	
-	def case_start(self, case):
-		self.case = case
-		self.correct = False
-		self.ntotal += 1
-		if case.points:
-			self.nvalued += 1
-	
-	def case_correct(self):
-		self.correct = True
-		self.ncorrect += 1
-		if self.case.points:
-			self.ncorrectvalued += 1
-	
-	def case_end(self):
-		self.log.append((self.case, self.correct))
-		del self.case
-		if not self.correct:
-			self.allcorrect = False
-	
-	def score(self, real, max):
-		self.real += real
-		self.max += max
-	
-	def end(self):
-		if not self.allcorrect:
-			self.real = 0
-		if self.points is not None and self.points != self.max:
-			max, weighted = self.points, self.real * self.points / self.max if self.max else 0
-			before_weighting = ' (%g/%g before weighting)' % (self.real, self.max)
-		else:
-			max, weighted = self.max, self.real
-			before_weighting = ''
-		say('Group total: %d/%d tests, %g/%g points%s' % (self.ncorrect, self.ntotal, weighted, max, before_weighting))
-		# No real need to flush stdout, as it will anyway be flushed in a moment,
-		# when either the problem total or the next test case's ID is printed
-		return weighted, max, self.log
-
-class Problem(object):
-	__slots__ = 'name', 'config', 'cache', 'testcases'
-	
-	def __init__(prob, name):
-		if not isinstance(name, basestring):
-			# This shouldn't happen, of course
-			raise TypeError('Problem() argument 1 must be string, not ' + type(name).__name__)
-		prob.name = name
-		prob.config = config.load_problem(name)
-		prob.cache = Cache({'padoutput': 0})
-		prob.testcases = testcases.load_problem(prob)
-	
-	# TODO
-	def build(prob):
-		raise NotImplementedError
-	
-	def test(prob):
-		case = None
-		try:
-			contexts = deque((TestGroup(),))
-			for case in prob.testcases:
-				if case is test_context_end:
-					real, max, log = contexts.pop().end()
-					for case, correct in log:
-						contexts[-1].case_start(case)
-						if correct:
-							contexts[-1].case_correct()
-						contexts[-1].case_end()
-					contexts[-1].score(real, max)
-					continue
-				elif isinstance(case, TestContext):
-					contexts.append(case)
-					continue
-				contexts[-1].case_start(case)
-				granted = 0
-				id = str(case.id)
-				if case.isdummy:
-					id = 'sample ' + id
-				say('%*s: ' % (prob.cache.padoutput, id), end='')
-				sys.stdout.flush()
-				try:
-					if prob.config.kind != 'outonly':
-						granted = case(lambda: (say('%7.3f%s s, ' % (case.time_stopped - case.time_started, case.time_limit_string), end=''), sys.stdout.flush()))
-					else:
-						granted = case(lambda: None)
-				except testcases.TestCaseSkipped:
-					verdict = 'skipped due to skimming mode'
-				except testcases.CanceledByUser:
-					verdict = 'canceled by the user'
-				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:
-					e = sys.exc_info()[1]
-					if e.comment:
-						verdict = 'wrong answer (%s)' % e.comment
-					else:
-						verdict = 'wrong answer'
-				except testcases.NonZeroExitCode:
-					e = sys.exc_info()[1]
-					if e.exitcode < 0:
-						if sys.platform == 'win32':
-							verdict = 'terminated with error 0x%X' % (e.exitcode + 0x100000000)
-						elif -e.exitcode in signalnames:
-							verdict = 'terminated by signal %d (%s)' % (-e.exitcode, signalnames[-e.exitcode])
-						else:
-							verdict = 'terminated by signal %d' % -e.exitcode
-					else:
-						verdict = 'non-zero return code %d' % e.exitcode
-				except testcases.CannotStartTestee:
-					verdict = 'cannot launch the program to test%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.CannotStartValidator:
-					verdict = 'cannot launch the validator%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.CannotReadOutputFile:
-					verdict = 'cannot read the output file%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.CannotReadInputFile:
-					verdict = 'cannot read the input file%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.CannotReadAnswerFile:
-					verdict = 'cannot read the reference output file%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.ExceptionWrapper:
-					verdict = 'unspecified reason [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1].upstream)
-				except testcases.TestCaseNotPassed:
-					verdict = 'unspecified reason [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1])
-				#except Exception:
-				#	verdict = 'unknown error [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1])
-				else:
-					try:
-						granted, comment = granted
-					except TypeError:
-						comment = ''
-					else:
-						if comment:
-							comment = ' (%s)' % comment
-					if granted >= 1:
-						contexts[-1].case_correct()
-						prob.testcases.send(True)
-						verdict = 'OK' + comment
-					elif not granted:
-						verdict = 'wrong answer' + comment
-					else:
-						verdict = 'partly correct' + comment
-					granted *= case.points
-				say('%g/%g, %s' % (granted, case.points, verdict))
-				contexts[-1].case_end()
-				contexts[-1].score(granted, case.points)
-			weighted = contexts[0].real * prob.config.taskweight / contexts[0].max if contexts[0].max else 0
-			before_weighting = valued = ''
-			if prob.config.taskweight != contexts[0].max:
-				before_weighting = ' (%g/%g before weighting)' % (contexts[0].real, contexts[0].max)
-			if contexts[0].nvalued != contexts[0].ntotal:
-				valued = ' (%d/%d valued)' % (contexts[0].ncorrectvalued, contexts[0].nvalued)
-			say('Problem total: %d/%d tests%s, %g/%g points%s' % (contexts[0].ncorrect, contexts[0].ntotal, valued, weighted, prob.config.taskweight, before_weighting))
-			sys.stdout.flush()
-			return weighted, prob.config.taskweight
-		finally:
-			if options.erase and case and case.has_iofiles:
-				for var in 'in', 'out':
-					name = getattr(prob.config, var + 'name')
-					if name:
-						try:
-							os.remove(name)
-						except Exception:
-							pass
-				if case.has_ansfile:
-					if prob.config.ansname:
-						try:
-							os.remove(prob.config.ansname)
-						except Exception:
-							pass
\ No newline at end of file
--- a/publish.sh	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#! /bin/sh
-
-VERSION=`hg identify | awk '{ print $1 }'`
-if [ -z "$VERSION" ]
-then
-	echo The current Mercurial changeset could not be determined. >&2
-	exit 1
-fi
-
-sed 's/$$REV$\$/hg '"$VERSION/" upreckon-vcs >upreckon
-chmod +x upreckon
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,60 @@
+#! /usr/bin/env python
+try:
+	from setuptools import setup, Extension
+	from setuptools.command.build_ext import build_ext
+except ImportError:
+	from distutils.core import setup, Extension
+	from distutils.command.build_ext import build_ext
+from distutils.errors import CCompilerError
+from distutils import log
+import os
+
+class build_opt_ext(build_ext):
+	def build_extension(self, ext):
+		try:
+			build_ext.build_extension(self, ext)
+		except CCompilerError:
+			log.warn("failed to build native extension %s (skipping)",
+			         ext.name)
+
+scripts = ['upreckon/upreckon']
+if os.name == 'nt':
+	scripts.append('upreckon/upreckon.cmd')
+
+setup(name='upreckon',
+      version='2.01.0',
+      author='Oleg Oshmyan',
+      author_email='chortos@inbox.lv',
+      url='http://chortos.selfip.net/~astiob/test.py/',
+      #description='',
+      #long_description='',
+      download_url='https://bitbucket.org/astiob/upreckon/downloads',
+      #platforms=(),
+      #license='',
+      classifiers=(
+          'Development Status :: 5 - Production/Stable',
+          'Environment :: Console',
+          'Intended Audience :: Developers',
+          'License :: Freely Distributable',
+          'Natural Language :: English',
+          'Operating System :: Microsoft :: Windows',
+          'Operating System :: OS Independent',
+          'Operating System :: POSIX',
+          'Programming Language :: Python',
+          'Programming Language :: Python :: 2',
+          #'Programming Language :: Python :: 2.5',
+          'Programming Language :: Python :: 2.6',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.0',
+          'Programming Language :: Python :: 3.1',
+          'Programming Language :: Python :: 3.2',
+          'Topic :: Software Development :: Testing',
+          'Topic :: Utilities',
+      ),
+      ext_modules=[Extension('upreckon._unix',
+                             sources=['upreckon/_unixmodule.cpp'])],
+      packages=['upreckon'],
+      scripts=scripts,
+      cmdclass={'build_ext': build_opt_ext},
+     )
--- a/testcases.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-# TODO: copy the ansfile if not options.erase even if no validator is used
-
-from __future__ import division, with_statement
-
-from compat import *
-import files, problem, config
-from __main__ import options
-
-import glob, re, sys, tempfile, time
-from subprocess import Popen, PIPE, STDOUT
-
-import os
-devnull = open(os.path.devnull, 'w+')
-
-if options.autotime:
-	# This is really a dirty hack that assumes that sleep() does not spend
-	# the CPU time of the current process and that if clock() measures
-	# wall-clock time, then it is more precise than time() is. Both these
-	# assumptions are true on all platforms I have tested this on so far,
-	# but I am not aware of any guarantee that they will both be true
-	# on every other platform.
-	c = time.clock()
-	time.sleep(1)
-	c = time.clock() - c
-	if int(c + .5) == 1:
-		clock = time.clock
-	else:
-		clock = time.time
-
-class DummySignalIgnorer(object):
-	def __enter__(self): pass
-	def __exit__(self, exc_type, exc_value, traceback): pass
-signal_ignorer = DummySignalIgnorer()
-
-# win32 and unix are imported a bit later
-
-__all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed',
-           'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer',
-           'NonZeroExitCode', 'CannotStartTestee',
-           'CannotStartValidator', 'CannotReadOutputFile',
-           'CannotReadInputFile', 'CannotReadAnswerFile',
-           'MemoryLimitExceeded', 'CPUTimeLimitExceeded',
-           'WallTimeLimitExceeded')
-
-
-
-# Exceptions
-
-class TestCaseNotPassed(Exception): __slots__ = ()
-class TestCaseSkipped(TestCaseNotPassed): __slots__ = ()
-class TimeLimitExceeded(TestCaseNotPassed): __slots__ = ()
-class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
-class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
-class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = ()
-class CanceledByUser(TestCaseNotPassed): __slots__ = ()
-
-class WrongAnswer(TestCaseNotPassed):
-	__slots__ = 'comment'
-	def __init__(self, comment=''):
-		self.comment = comment
-
-class NonZeroExitCode(TestCaseNotPassed):
-	__slots__ = 'exitcode'
-	def __init__(self, exitcode):
-		self.exitcode = exitcode
-
-class ExceptionWrapper(TestCaseNotPassed):
-	__slots__ = 'upstream'
-	def __init__(self, upstream):
-		self.upstream = upstream
-
-class CannotStartTestee(ExceptionWrapper): __slots__ = ()
-class CannotStartValidator(ExceptionWrapper): __slots__ = ()
-class CannotReadOutputFile(ExceptionWrapper): __slots__ = ()
-class CannotReadInputFile(ExceptionWrapper): __slots__ = ()
-class CannotReadAnswerFile(ExceptionWrapper): __slots__ = ()
-
-# Import platform-specific code now that exception classes are defined
-try:
-	from win32 import *
-except Exception:
-	from unix import *
-
-
-
-# Helper context managers
-
-class CopyDeleting(object):
-	__slots__ = 'case', 'file', 'name'
-	
-	def __init__(self, case, file, name):
-		self.case = case
-		self.file = file
-		self.name = name
-	
-	def __enter__(self):
-		if self.name:
-			try:
-				self.file.copy(self.name)
-			except:
-				try:
-					self.__exit__(None, None, None)
-				except:
-					pass
-				raise
-	
-	def __exit__(self, exc_type, exc_val, exc_tb):
-		if self.name:
-			self.case.files_to_delete.append(self.name)
-
-
-class Copying(object):
-	__slots__ = 'file', 'name'
-	
-	def __init__(self, file, name):
-		self.file = file
-		self.name = name
-	
-	def __enter__(self):
-		if self.name:
-			self.file.copy(self.name)
-	
-	def __exit__(self, exc_type, exc_val, exc_tb):
-		pass
-
-
-
-# Test case types
-
-class TestCase(object):
-	__slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points',
-	             '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')
-	has_ansfile = has_iofiles = False
-	needs_realinname = True
-	
-	if ABCMeta:
-		__metaclass__ = ABCMeta
-	
-	def __init__(case, prob, id, isdummy, points):
-		case.problem = prob
-		case.id = id
-		case.isdummy = isdummy
-		case.points = points
-		case.maxcputime = case.problem.config.maxcputime
-		case.maxwalltime = case.problem.config.maxwalltime
-		case.maxmemory = case.problem.config.maxmemory
-		if case.maxcputime:
-			case.cpu_time_limit_string = '/%.3f' % case.maxcputime
-		else:
-			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:
-			if case.needs_realinname:
-				case.realinname = case.problem.config.testcaseinname
-			case.realoutname = case.problem.config.testcaseoutname
-		else:
-			if case.needs_realinname:
-				case.realinname = case.problem.config.dummyinname
-			case.realoutname = case.problem.config.dummyoutname
-	
-	@abstractmethod
-	def test(case):
-		raise NotImplementedError
-	
-	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 getattr(case, 'time_started', None) is None:
-				case.time_started = case.time_stopped = now
-			elif getattr(case, 'time_stopped', None) is None:
-				case.time_stopped = now
-			if not case.has_called_back:
-				callback()
-			case.cleanup()
-	
-	def cleanup(case):
-		# Note that native extensions clean up on their own
-		# and never let this condition be satisfied
-		if getattr(case, 'process', None) and case.process.returncode is None:
-			kill(case.process)
-		for name in case.files_to_delete:
-			try:
-				os.remove(name)
-			except OSError:
-				# It can't be helped
-				pass
-	
-	def open_infile(case):
-		try:
-			case.infile = files.File('/'.join((case.problem.name, case.realinname.replace('$', case.id))))
-		except IOError:
-			e = sys.exc_info()[1]
-			raise CannotReadInputFile(e)
-	
-	def open_outfile(case):
-		try:
-			case.outfile = files.File('/'.join((case.problem.name, case.realoutname.replace('$', case.id))))
-		except IOError:
-			e = sys.exc_info()[1]
-			raise CannotReadAnswerFile(e)
-
-
-class SkippedTestCase(TestCase):
-	__slots__ = ()
-	
-	def test(case, callback):
-		raise TestCaseSkipped
-
-
-class ValidatedTestCase(TestCase):
-	__slots__ = 'validator'
-	
-	def __init__(case, *args):
-		TestCase.__init__(case, *args)
-		if not case.problem.config.tester:
-			case.validator = None
-		else:
-			case.validator = case.problem.config.tester
-	
-	def validate(case, output):
-		if not case.validator:
-			# Compare the output with the reference output
-			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:
-						raise WrongAnswer
-			return 1
-		elif callable(case.validator):
-			return case.validator(output)
-		else:                 
-			# Call the validator program
-			output.close()
-			if case.problem.config.ansname:
-				case.open_outfile()
-				case.outfile.copy(case.problem.config.ansname)
-			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])
-			with signal_ignorer:
-				comment = case.process.communicate()[0].strip()
-			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:
-				if case.process.returncode:
-					raise WrongAnswer(comment)
-				else:
-					return 1, comment
-			else:
-				return case.process.returncode / case.problem.config.maxexitcode, comment
-
-
-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()
-		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
-				# FIXME: 2.5 lacks the delete parameter
-				with tempfile.NamedTemporaryFile(delete=False) as f:
-					inputdatafname = f.name
-				contextmgr = CopyDeleting(case, case.infile, inputdatafname)
-			else:
-				inputdatafname = case.problem.config.inname
-				contextmgr = Copying(case.infile, inputdatafname)
-			with contextmgr:
-				with open(inputdatafname) as infile:
-					with tempfile.TemporaryFile('w+') if options.erase and (not case.validator or callable(case.validator)) else open(case.problem.config.outname, 'w+') as outfile:
-						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:
-							raise NonZeroExitCode(case.process.returncode)
-						case.has_called_back = True
-						callback()
-						outfile.seek(0)
-						return case.validate(outfile)
-		else:
-			case.infile.copy(case.problem.config.inname)
-			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)
-			case.has_called_back = True
-			callback()
-			try:
-				output = open(case.problem.config.outname, 'rU')
-			except IOError:
-				raise CannotReadOutputFile(sys.exc_info()[1])
-			with output as output:
-				return case.validate(output)
-
-
-# This is the only test case type not executing any programs to be tested
-class OutputOnlyTestCase(ValidatedTestCase):
-	__slots__ = ()
-	needs_realinname = False
-	
-	def cleanup(case):
-		pass
-	
-	def test(case, callback):
-		case.time_stopped = case.time_started = 0
-		case.has_called_back = True
-		callback()
-		try:
-			output = open(case.problem.config.outname.replace('$', case.id), 'rU')
-		except IOError:
-			raise CannotReadOutputFile(sys.exc_info()[1])
-		with output as output:
-			return case.validate(output)
-
-
-class BestOutputTestCase(ValidatedTestCase):
-	__slots__ = ()
-
-
-# This is the only test case type executing two programs simultaneously
-class ReactiveTestCase(TestCase):
-	__slots__ = ()
-	# The basic idea is to launch the program to be tested and the grader
-	# and to pipe their standard I/O from and to each other,
-	# and then to capture the grader's exit code and use it
-	# like the exit code of an output validator is used.
-
-
-class DummyTestContext(problem.TestGroup):
-	__slots__ = ()
-	def end(self):
-		say('Sample total: %d/%d tests' % (self.ncorrect, self.ntotal))
-		return 0, 0, self.log
-
-def load_problem(prob, _types={'batch'   : BatchTestCase,
-                               'outonly' : OutputOnlyTestCase,
-                               'bestout' : BestOutputTestCase,
-                               'reactive': ReactiveTestCase}):
-	# We will need to iterate over these configuration variables twice
-	try:
-		len(prob.config.dummies)
-	except Exception:
-		prob.config.dummies = tuple(prob.config.dummies)
-	try:
-		len(prob.config.tests)
-	except Exception:
-		prob.config.tests = tuple(prob.config.tests)
-	
-	if options.legacy:
-		prob.config.usegroups = False
-		newtests = []
-		for i, name in enumerate(prob.config.tests):
-			# Same here; we'll need to iterate over them twice
-			try:
-				l = len(name)
-			except Exception:
-				try:
-					name = tuple(name)
-				except TypeError:
-					name = (name,)
-				l = len(name)
-			if l > 1:
-				prob.config.usegroups = True
-			newtests.append(name)
-		if prob.config.usegroups:
-			prob.config.tests = newtests
-		del newtests
-	
-	# Even if they have duplicate test identifiers, we must honour sequence pointmaps
-	if isinstance(prob.config.pointmap, dict):
-		def getpoints(i, j, k=None):
-			try:
-				return prob.config.pointmap[i]
-			except KeyError:
-				try:
-					return prob.config.pointmap[None]
-				except KeyError:
-					return prob.config.maxexitcode or 1
-	elif prob.config.usegroups:
-		def getpoints(i, j, k):
-			try:
-				return prob.config.pointmap[k][j]
-			except LookupError:
-				return prob.config.maxexitcode or 1
-	else:
-		def getpoints(i, j):
-			try:
-				return prob.config.pointmap[j]
-			except LookupError:
-				return prob.config.maxexitcode or 1
-	
-	# First get prob.cache.padoutput right,
-	# then yield the actual test cases
-	for i in prob.config.dummies:
-		s = 'sample ' + str(i).zfill(prob.config.paddummies)
-		prob.cache.padoutput = max(prob.cache.padoutput, len(s))
-	if prob.config.usegroups:
-		if not isinstance(prob.config.groupweight, dict):
-			prob.config.groupweight = dict(enumerate(prob.config.groupweight))
-		for group in prob.config.tests:
-			for i in group:
-				s = str(i).zfill(prob.config.padtests)
-				prob.cache.padoutput = max(prob.cache.padoutput, len(s))
-		if prob.config.dummies:
-			yield DummyTestContext()
-			for i in prob.config.dummies:
-				s = str(i).zfill(prob.config.paddummies)
-				if (yield _types[prob.config.kind](prob, s, True, 0)):
-					yield
-			yield problem.test_context_end
-		for k, group in enumerate(prob.config.tests):
-			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)
-				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:
-			s = str(i).zfill(prob.config.padtests)
-			prob.cache.padoutput = max(prob.cache.padoutput, len(s))
-		for i in prob.config.dummies:
-			s = str(i).zfill(prob.config.paddummies)
-			if (yield _types[prob.config.kind](prob, s, True, 0)):
-				yield
-		for j, i in enumerate(prob.config.tests):
-			s = str(i).zfill(prob.config.padtests)
-			if (yield _types[prob.config.kind](prob, s, False, getpoints(i, j))):
-				yield
\ No newline at end of file
--- a/unix.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-
-from compat import *
-import testcases  # mutual import
-
-from subprocess import Popen
-import os, sys, time
-
-try:
-	from testcases import 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
-except ImportError:
-	SIGTERM = 15
-	SIGKILL = 9
-
-__all__ = 'call', 'kill', 'pause', 'clock'
-
-
-try:
-	from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd
-	from select import select, error as SelectError
-	from errno import EAGAIN, EINTR
-	from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL
-	from os import O_NONBLOCK
-	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 testcases.CannotStartTestee(sys.exc_info()[1])
-		case.time_started = clock()
-		if not case.maxwalltime:
-			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.maxwalltime
-			while True:
-				exitcode, now = case.process.poll(), clock()
-				if exitcode is not None:
-					case.time_stopped = now
-					break
-				elif now >= time_end:
-					raise testcases.WallTimeLimitExceeded
-				else:
-					time.sleep(.001)
-else:
-	try:
-		from fcntl import FD_CLOEXEC
-	except ImportError:
-		FD_CLOEXEC = 1
-	
-	try:
-		from signal import siginterrupt
-	except ImportError:
-		# Sucks.
-		siginterrupt = lambda signalnum, flag: None
-	
-	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 as RLIMIT_AS
-	except ImportError:
-		setrlimit = None
-	
-	# 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)
-	def bury_child(signum, frame):
-		try:
-			bury_child.case.time_stopped = clock()
-		except Exception:
-			pass
-	signal(SIGCHLD, bury_child)
-	set_wakeup_fd(sigchld_pipe_write)
-	class SignalIgnorer(object):
-		def __enter__(self):
-			signal(SIGCHLD, SIG_DFL)
-		def __exit__(self, exc_type, exc_value, traceback):
-			signal(SIGCHLD, bury_child)
-	signal_ignorer = SignalIgnorer()
-	__all__ += 'signal_ignorer',
-	
-	# 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
-		# So how the hell do I actually make use of pass_fds?
-		# On 3.1-, calling Popen with pass_fds prints an exception
-		# from Popen.__del__ to stderr. On 3.2, Popen without close_fds
-		# or pass_fds creates a child and fails but that of course
-		# generates a SIGCHLD, which causes problems, and I have
-		# no process ID to wait upon to negate the changes made
-		# by the SIGCHLD handler.
-		kwargs['close_fds'] = False
-		old_rusage = getrusage(RUSAGE_CHILDREN)
-		last_rusage = None
-		while True:
-			try:
-				os.read(sigchld_pipe_read, 512)
-			except OSError:
-				if sys.exc_info()[1].errno == EAGAIN:
-					break
-				else:
-					raise
-		siginterrupt(SIGCHLD, False)
-		try:
-			case.process = Popen(*args, **kwargs)
-		except OSError:
-			os.close(read)
-			raise testcases.CannotStartTestee(sys.exc_info()[1])
-		finally:
-			siginterrupt(SIGCHLD, True)
-			os.close(write)
-		try:
-			if not catch_escape:
-				if case.maxwalltime:
-					try:
-						select((sigchld_pipe_read,), (), (), case.maxwalltime)
-					except SelectError:
-						if sys.exc_info()[1].args[0] != EINTR:
-							raise
-					# subprocess in Python 2.6- is not guarded against EINTR
-					try:
-						if case.process.poll() is None:
-							raise testcases.WallTimeLimitExceeded
-					except OSError:
-						if sys.exc_info()[1].errno != EINTR:
-							raise
-						else:
-							case.process.poll()
-				else:
-					wait(case.process)
-			else:
-				if not case.maxwalltime:
-					try:
-						while case.process.poll() is None:
-							s = select((sys.stdin, sigchld_pipe_read), (), ())
-							if (s[0] == [sys.stdin] and
-							    sys.stdin.read(1) == '\33'):
-								raise testcases.CanceledByUser
-					except (SelectError, IOError, OSError):
-						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:
-								s = select((sys.stdin, sigchld_pipe_read),
-								           (), (), remaining)
-								if (s[0] == [sys.stdin] and
-								    sys.stdin.read(1) == '\33'):
-									raise testcases.CanceledByUser
-							else:
-								raise testcases.WallTimeLimitExceeded
-					except (SelectError, IOError, OSError):
-						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 (case.maxwalltime and
-		    case.time_stopped - case.time_started > case.maxwalltime):
-			raise testcases.WallTimeLimitExceeded
-		if new_rusage:
-			time_started = old_rusage.ru_utime + old_rusage.ru_stime + cpustart
-			time_stopped = new_rusage.ru_utime + new_rusage.ru_stime
-			# Yes, this actually happens
-			if time_started > time_stopped:
-				time_started = time_stopped
-			if case.maxcputime or not case.maxwalltime:
-				case.time_started = time_started
-				case.time_stopped = time_stopped
-				case.time_limit_string = case.cpu_time_limit_string
-				if (case.maxcputime and
-				    time_stopped - time_started > 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)
-	wait(process)
-
-
-# subprocess in Python 2.6- is not guarded against EINTR
-try:
-	from errno import EINTR
-except ImportError:
-	wait = Popen.wait
-else:
-	def wait(process):
-		while True:
-			try:
-				return process.wait()
-			except OSError:
-				if sys.exc_info()[1].errno != EINTR:
-					raise
-
-
-try:
-	from _unix import *
-except ImportError:
-	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 = lambda: sys.stdin.read(1)
-			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)
--- a/upreckon-vcs	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-#! /usr/bin/env python
-# Copyright (c) 2009-2011 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-import optparse, sys, compat
-
-from compat import *
-
-version = '2.01.0 ($$REV$$)'
-parser = optparse.OptionParser(version='Upreckon '+version, epilog='Python 2.5 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('-u', '--update', dest='update', action='store_true', default=False, help='update the installed Upreckon to the latest publicly available version')
-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('-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; 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()
-parser.destroy()
-del parser
-
-if options.update:
-	try:
-		urllib, urlread = compat.import_urllib()
-	except ImportError:
-		sys.exit('Error: the urllib Python module is missing. Without it, an automatic update is impossible.')
-	
-	latesttext = urlread('http://chortos.selfip.net/~astiob/test.py/version.txt')
-	latest = latesttext.split('.')
-	installed = version.split('.')
-	update = None
-	
-	if latest[0] > installed[0]:
-		update = 'major'
-	elif latest[0] == installed[0]:
-		if latest[1] > installed[1]:
-			update = 'feature'
-		elif latest[1] == installed[1]:
-			if latest[2] > installed[2]:
-				update = 'bug-fixing'
-			elif latest[2] == installed[2]:
-				say('You are using the latest publicly available version of Upreckon (%s).' % latesttext)
-				sys.exit()
-	
-	if not update:
-		say('Your copy of Upreckon is newer (%s) than the publicly available version (%s).' % (version, latesttext))
-		sys.exit()
-	
-	say('A %s update to Upreckon is available (%s). Downloading...' % (update, latesttext))
-	sys.stdout.flush()
-	# FIXME: need to update all files!
-	urllib.urlretrieve('http://chortos.selfip.net/~astiob/test.py/test.py', sys.argv[0])
-	say('Downloaded and installed. Now you are using Upreckon %s.' % latesttext)
-	sys.exit()
-
-import config, itertools, os, subprocess, sys, time
-
-if options.legacy:
-	compat.pseudobuiltins += 'xrange',
-
-if options.list_problems:
-	options.pause = False
-
-import testcases
-
-try:
-	from testcases import pause
-except ImportError:
-	pause = None
-
-try:
-	globalconf = config.load_global()
-	
-	# Do this check here so that if we have to warn them, we do it as early as possible
-	if options.pause and not pause and not hasattr(globalconf, 'pause'):
-		if os.name == 'posix':
-			globalconf.pause = 'read -s -n 1'
-			say('Warning: configuration variable pause is not defined; it was devised automatically but the choice might be incorrect, so Upreckon might exit immediately after the testing is completed.', file=sys.stderr)
-			sys.stderr.flush()
-		elif os.name == 'nt':
-			globalconf.pause = 'pause'
-		else:
-			sys.exit('Error: configuration variable pause is not defined and cannot be devised automatically.')
-	
-	from problem import *
-	
-	# Support single-problem configurations
-	if globalconf.problems is None:
-		shouldprintnames = False
-		globalconf.multiproblem = False
-		globalconf.problems = os.path.curdir,
-	else:
-		globalconf.multiproblem = True
-		shouldprintnames = True
-	
-	if options.list_problems:
-		for taskname in globalconf.problems:
-			say(taskname)
-		sys.exit()
-	
-	ntasks = 0
-	nfulltasks = 0
-	maxscore = 0
-	realscore = 0
-	
-	for taskname in (globalconf.problems if not options.problems else options.problems):
-		problem = Problem(taskname)
-		
-		if ntasks and not options.copyonly: say()
-		if shouldprintnames: say(taskname)
-		
-		if options.copyonly:
-			problem.copytestdata()
-		else:
-			real, max = problem.test()
-		
-		ntasks += 1
-		nfulltasks += real == max
-		realscore += real
-		maxscore += max
-	
-	if options.copyonly:
-		sys.exit()
-	
-	if ntasks != 1:
-		say()
-		say('Grand total: %g/%g weighted points; %d/%d problems solved fully' % (realscore, maxscore, nfulltasks, ntasks))
-except KeyboardInterrupt:
-	say('Exiting due to a keyboard interrupt.', end='', file=sys.stderr)
-	sys.stderr.flush()
-	try:
-		import os, signal
-		signal.signal(signal.SIGINT, signal.SIG_DFL)
-		os.kill(os.getpid(), signal.SIGINT)
-	except Exception:
-		pass
-	# Do this even if we got no exceptions, just in case
-	say(file=sys.stderr)
-	sys.exit(1)
-
-if options.pause:
-	say('Press any key to exit...')
-	sys.stdout.flush()
-	
-	if pause:
-		pause()
-	elif callable(globalconf.pause):
-		globalconf.pause()
-	else:
-		with open(os.devnull, 'w') as devnull:
-			subprocess.call(globalconf.pause, shell=True, stdout=devnull, stderr=subprocess.STDOUT)
\ No newline at end of file
--- a/upreckon.cmd	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-@ start /b /wait python "%~dpn0" %*
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/_unixmodule.cpp	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,1449 @@
+// Copyright (c) 2011 Chortos-2 <chortos@inbox.lv>
+
+#include <Python.h>
+#include <structmember.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include <limits.h>
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#ifdef __APPLE__
+#pragma weak_import posix_spawnp
+#endif
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#if !(defined __cplusplus) && !(defined bool)
+#ifdef HAVE_C99_BOOL
+#define bool _Bool
+#else
+#define bool char
+#endif
+#undef true
+#define true 1
+#undef false
+#define false 0
+#endif
+
+// On Python 2.5, SIGINT handling may get delayed until we return to Python
+#if PY_MAJOR_VERSION > 2 || PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6
+#define USE_WAKEUP_FD
+#endif
+
+#if !(defined RLIMIT_AS) && defined RLIMIT_VMEM
+#define RLIMIT_AS RLIMIT_VMEM
+#endif
+
+// Condition stolen from posixmodule.c of Python 2.7.1
+#if defined __USLC__ && defined __SCO_VERSION__  // SCO UDK Compiler
+//#ifdef HAVE_FORK1
+#define fork fork1
+#endif
+
+// Stolen from posixmodule.c of Python 2.7.1
+#ifdef WITH_NEXT_FRAMEWORK
+#include <crt_externs.h>
+static char **environ = NULL;
+#elif !(defined _MSC_VER) && (!(defined __WATCOMC__) || defined __QNX__)
+extern char **environ;
+#endif
+
+#ifndef Py_PYTIME_H
+typedef struct timeval _PyTime_timeval;
+#ifndef GETTIMEOFDAY_NO_TZ
+#define _PyTime_gettimeofday(tvp) gettimeofday((tvp), NULL)
+#else
+#define _PyTime_gettimeofday(tvp) gettimeofday((tvp))
+#endif
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+#define PyInt_AsLong PyLong_AsLong
+#define PyInt_FromLong PyLong_FromLong
+#define PyNumber_Int PyNumber_Long
+#endif
+
+#define TESTEE_SPAWNED 0
+#define TESTEE_SPAWN_FAILED 1
+#define TESTEE_REPORT_STATUS(status) \
+	do \
+	{ \
+		const char c = (status); \
+		write(c2ppipe[1], &c, 1); \
+	} \
+	while (0)
+
+#if !(defined SIGKILL) && defined SIGTERM
+#define SIGKILL SIGTERM
+#endif
+
+#if defined HAVE_KILL && defined SIGKILL
+#ifdef HAVE_WAITPID
+#define TERM_TESTEE \
+	do \
+	{ \
+		kill(-curpid, SIGKILL); \
+		kill(-curpid, SIGCONT); \
+		while (waitpid(curpid, &retstat, 0) != curpid); \
+	} \
+	while (0)
+#else
+#define TERM_TESTEE \
+	do \
+	{ \
+		kill(-curpid, SIGKILL); \
+		kill(-curpid, SIGCONT); \
+		while (wait(&retstat) != curpid); \
+	} \
+	while (0)
+#endif
+#else
+#define TERM_TESTEE
+#endif
+
+#if defined HAVE_KILL && defined SIGINT
+#define PROPAGATE_SIGINT ((void) kill(-curpid, SIGINT))
+#else
+#define PROPAGATE_SIGINT
+#endif
+
+struct child_stats
+{
+	int returncode;
+	_PyTime_timeval walltime;
+#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
+	_PyTime_timeval cputime;
+	Py_ssize_t memory;
+#endif
+};
+
+static pid_t curpid;
+static const struct child_stats zero_stats = { 0 };
+static PyObject *CannotStartTestee, *CanceledByUser, *WallTimeLimitExceeded,
+                *CPUTimeLimitExceeded, *MemoryLimitExceeded;
+static _PyTime_timeval time_end;
+
+#ifdef USE_WAKEUP_FD
+static char dont_care_buffer[512];
+static int intpipe[2] = { 0 };
+#endif
+
+#ifdef HAVE_TERMIOS_H
+static bool catch_escape = false;
+static struct termios orig_termios;
+#endif
+
+typedef struct
+{
+	PyObject_HEAD
+	int returncode;
+} _unix__PopenPlaceholderObject;
+
+static PyMemberDef _PopenPlaceholder_members[] =
+{
+	{ "returncode", T_INT, offsetof(_unix__PopenPlaceholderObject, returncode), READONLY, NULL },
+	{ NULL }
+};
+
+static PyTypeObject _unix__PopenPlaceholderType =
+{
+#if PY_MAJOR_VERSION >= 3
+	PyVarObject_HEAD_INIT(NULL, 0)
+#else
+	PyObject_HEAD_INIT(NULL)
+	0,                                        /*ob_size*/
+#endif
+	"_unix._PopenPlaceholder",                /*tp_name*/
+	sizeof(_unix__PopenPlaceholderObject),    /*tp_basicsize*/
+	0,                                        /*tp_itemsize*/
+	0,                                        /*tp_dealloc*/
+	0,                                        /*tp_print*/
+	0,                                        /*tp_getattr*/
+	0,                                        /*tp_setattr*/
+	0,                                        /*tp_compare*/
+	0,                                        /*tp_repr*/
+	0,                                        /*tp_as_number*/
+	0,                                        /*tp_as_sequence*/
+	0,                                        /*tp_as_mapping*/
+	0,                                        /*tp_hash */
+	0,                                        /*tp_call*/
+	0,                                        /*tp_str*/
+	0,                                        /*tp_getattro*/
+	0,                                        /*tp_setattro*/
+	0,                                        /*tp_as_buffer*/
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+	0,                                        /*tp_doc*/
+	0,                                        /*tp_traverse*/
+	0,                                        /*tp_clear*/
+	0,                                        /*tp_richcompare*/
+	0,                                        /*tp_weaklistoffset*/
+	0,                                        /*tp_iter*/
+	0,                                        /*tp_iternext*/
+	0,                                        /*tp_methods*/
+	_PopenPlaceholder_members,                /*tp_members*/
+};
+
+#ifndef timeradd
+#define timeradd(a, b, res) \
+	do \
+	{ \
+		(res)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
+		(res)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
+		if ((res)->tv_usec >= 1000000) \
+		{ \
+			++(res)->tv_sec; \
+			(res)->tv_usec -= 1000000; \
+		} \
+	} \
+	while (0)
+#endif
+
+#ifndef timersub
+#define timersub(a, b, res) \
+	do \
+	{ \
+		(res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+		(res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+		if ((res)->tv_usec < 0) \
+		{ \
+			--(res)->tv_sec; \
+			(res)->tv_usec += 1000000; \
+		} \
+	} \
+	while (0)
+#endif
+
+#ifndef timerclear
+#define timerclear(tvp) ((void) ((tvp)->tv_sec = (tvp)->tv_usec = 0))
+#endif
+
+#ifndef timerisset
+#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
+#endif
+
+#ifndef timercmp
+#define timercmp(a, b, cmp) \
+	(((a)->tv_sec == (b)->tv_sec) \
+		? ((a)->tv_usec cmp (b)->tv_usec) \
+		: ((a)->tv_sec cmp (b)->tv_sec))
+#endif
+
+// Stolen from posixmodule.c of Python 2.7.1
+static void free_string_array(char **array, Py_ssize_t count)
+{
+	Py_ssize_t i;
+	for (i = 0; i < count; ++i)
+		PyMem_Free(array[i]);
+	PyMem_DEL(array);
+}
+
+// Stolen from termios.c of Python 2.7.1
+static int fdconv(PyObject *obj, void *p)
+{
+	int fd = PyObject_AsFileDescriptor(obj);
+	if (fd >= 0)
+	{
+		*((int *) p) = fd;
+		return 1;
+	}
+	return 0;
+}
+
+// Parts stolen from bltinmodule.c, posixmodule.c and termios.c of Python 2.7.1
+static int my_spawn(PyObject *args, PyObject *kwds, int c2ppipe[2], int maxcputime, Py_ssize_t maxmemory)
+{
+	static const char *const kwlist[] = { "stdin", "stdout", "stderr", NULL };
+	static PyObject *dummy_args = NULL;
+	Py_ssize_t i, argc;
+	char **argv;
+	bool own_args = false;
+	int fdin = 0, fdout = 1, fderr = 2;
+	
+	if (dummy_args == NULL)
+	{
+		if (!(dummy_args = PyTuple_New(0)))
+		{
+			return -1;
+		}
+	}
+	
+	if (!PyArg_ParseTuple(args, "O:call", &args))
+	{
+		return -1;
+	}
+	if (!PyArg_ParseTupleAndKeywords(dummy_args, kwds, "|O&O&O&:call", (char **) kwlist, fdconv, &fdin, fdconv, &fdout, fdconv, &fderr))
+	{
+		return -1;
+	}
+	
+#if PY_MAJOR_VERSION >= 3
+	if (PyUnicode_Check(args))
+#else
+	if (PyString_Check(args) || PyUnicode_Check(args))
+#endif
+	{
+		argc = 1;
+		args = PyTuple_Pack(1, args);
+		if (args == NULL)
+		{
+			return -1;
+		}
+		own_args = true;
+	}
+	else if (!PySequence_Check(args))
+	{
+		PyErr_SetString(PyExc_TypeError, "call() argument must be a sequence or string");
+		return -1;
+	}
+	else
+	{
+		argc = PySequence_Size(args);
+		if (argc < 1)
+		{
+			PyErr_SetString(PyExc_TypeError, "call() argument must not be empty");
+			return -1;
+		}
+	}
+	
+	argv = PyMem_NEW(char *, argc + 1);
+	if (argv == NULL)
+	{
+		if (own_args)
+		{
+			Py_DECREF(args);
+		}
+		PyErr_NoMemory();
+		return -1;
+	}
+	
+	for (i = 0; i < argc; ++i)
+	{
+		if (!PyArg_Parse(PySequence_ITEM(args, i), "et", Py_FileSystemDefaultEncoding, &argv[i]))
+		{
+			free_string_array(argv, i);
+			if (own_args)
+			{
+				Py_DECREF(args);
+			}
+			PyErr_SetString(PyExc_TypeError, "call() argument must contain only strings");
+			return -1;
+		}
+	}
+	argv[argc] = NULL;
+	
+	curpid = fork();
+	if (!curpid)
+	{
+		pid_t pid;
+		int spawn_errno, status, fd, fddupped[3];
+		struct child_stats stats;
+		_PyTime_timeval tvstart, tvend;
+#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
+		struct rusage rusage;
+#endif
+#if defined RLIMIT_AS || defined RLIMIT_CPU
+		struct rlimit rlimit;
+#endif
+		
+		/*
+		Assume no errors occur:
+		* POSIX:2008 doesn't even define any errors for setpgrp,
+		  nor does the (probably copied-verbatim-from-FreeBSD) man page
+		  on Mac OS X 10.6;
+		* none of the error conditions POSIX:2008 does define
+		  for setpgid can occur.
+		*/
+#ifdef HAVE_SETPGID
+		setpgid(0, 0);
+#else //if defined HAVE_SETPGRP
+#ifdef SETPGRP_HAVE_ARG
+		setpgrp(0, 0);
+#else
+		setpgrp();
+#endif
+#endif
+		
+#ifdef SIGINT
+		signal(SIGINT, SIG_DFL);
+#endif
+		
+#if PY_MAJOR_VERSION > 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2
+		_Py_RestoreSignals();
+#else
+#ifdef SIGPIPE
+		signal(SIGPIPE, SIG_DFL);
+#endif
+#ifdef SIGXFSZ
+		signal(SIGXFSZ, SIG_DFL);
+#endif
+#ifdef SIGXFZ
+		signal(SIGXFZ, SIG_DFL);
+#endif
+#endif
+		
+		if (c2ppipe[1] < 3)
+		{
+			int newfd;
+#ifdef F_DUPFD_CLOEXEC
+			newfd = fcntl(c2ppipe[1], F_DUPFD_CLOEXEC, 3);
+			if (newfd == -1)
+			{
+				spawn_errno = errno;
+				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
+				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
+				_exit(127);
+			}
+			c2ppipe[1] = newfd;
+#else
+			newfd = fcntl(c2ppipe[1], F_DUPFD, 3);
+			// Other threads should not fork/spawn right now
+			if (newfd == -1
+			 || fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) | FD_CLOEXEC) == -1)
+			{
+				spawn_errno = errno;
+				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
+				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
+				_exit(127);
+			}
+			c2ppipe[1] = newfd;
+#endif
+		}
+		// Yes, this works as intended even if fdin == fdout == fderr == 0
+		// and there are no open file descriptors except 0 and c2ppipe
+		// FIXME: error handling
+		fddupped[0] = dup(fdin);
+		fddupped[1] = dup(fdout);
+		fddupped[2] = dup(fderr);
+		dup2(fddupped[0], 0);
+		dup2(fddupped[1], 1);
+		dup2(fddupped[2], 2);
+		// FIXME: close() may fail with EINTR or EIO; is setting CLOEXEC safer?
+		// Bear in mind we still want to close them in _this_ process
+		for (fd = sysconf(_SC_OPEN_MAX); --fd > c2ppipe[1]; )
+		{
+			close(fd);
+		}
+		while (--fd >= 3)
+		{
+			close(fd);
+		}
+		
+#ifdef RLIMIT_AS
+		if (maxmemory)
+		{
+			rlimit.rlim_cur = rlimit.rlim_max = maxmemory;
+			setrlimit(RLIMIT_AS, &rlimit);
+		}
+#endif
+#ifdef RLIMIT_CPU
+		if (maxcputime)
+		{
+			rlimit.rlim_cur = rlimit.rlim_max = maxcputime;
+			setrlimit(RLIMIT_CPU, &rlimit);
+		}
+#endif
+		
+#ifdef HAVE_SPAWN_H
+#ifdef __APPLE__
+		if (posix_spawnp != NULL)
+		{
+#endif
+			spawn_errno = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ);
+			_PyTime_gettimeofday(&tvstart);
+			
+			if (spawn_errno)
+			{
+				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
+				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
+				_exit(127);
+			}
+#ifdef __APPLE__
+		}
+		else
+#endif
+#endif
+#if !(defined HAVE_SPAWN_H) || defined __APPLE__
+		{
+			pid = fork();
+			if (!pid)
+			{
+				execvp(argv[0], argv);
+				spawn_errno = errno;
+				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
+				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
+				_exit(127);
+			}
+			else if (pid == -1)
+			{
+				spawn_errno = errno;
+				TESTEE_REPORT_STATUS(TESTEE_SPAWN_FAILED);
+				write(c2ppipe[1], &spawn_errno, sizeof spawn_errno);
+				_exit(127);
+			}
+			else
+			{
+				_PyTime_gettimeofday(&tvstart);
+			}
+		}
+#endif
+		TESTEE_REPORT_STATUS(TESTEE_SPAWNED);
+		write(c2ppipe[1], &tvstart, sizeof tvstart);
+		
+#ifdef HAVE_WAIT4
+		while (wait4(pid, &status, 0, &rusage) != pid);
+#elif defined HAVE_WAIT3
+		while (wait3(&status, 0, &rusage) != pid);
+#elif defined HAVE_WAITPID
+		while (waitpid(pid, &status, 0) != pid);
+#else
+		while (wait(&status) != pid);
+#endif
+		
+		_PyTime_gettimeofday(&tvend);
+#if defined HAVE_SYS_RESOURCE_H && !(defined HAVE_WAIT4 || defined HAVE_WAIT3)
+		getrusage(RUSAGE_CHILDREN, &rusage);
+#endif
+		
+		stats = zero_stats;
+		
+		if (WIFEXITED(status) && WEXITSTATUS(status) == 127) _exit(127);
+		else if (WIFSIGNALED(status)) stats.returncode = -WTERMSIG(status);
+		else stats.returncode = WEXITSTATUS(status);
+		
+		timersub(&tvend, &tvstart, &stats.walltime);
+#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
+		timeradd(&rusage.ru_utime, &rusage.ru_stime, &stats.cputime);
+#ifdef __APPLE__
+		stats.memory = rusage.ru_maxrss;
+#else
+		stats.memory = rusage.ru_maxrss << 10;
+#endif
+#endif
+		
+		write(c2ppipe[1], &stats, sizeof stats);
+		_exit(0);
+	}
+	else if (curpid == -1)
+	{
+		PyErr_SetFromErrno(PyExc_OSError);
+		free_string_array(argv, argc);
+		if (own_args)
+		{
+			Py_DECREF(args);
+		}
+		return 0;
+	}
+	
+	/*
+	Assume no errors occur if the child is still alive:
+	* the (probably copied-verbatim-from-FreeBSD) man page
+	  on Mac OS X 10.6 doesn't even define any errors for setpgrp;
+	* none of the error conditions POSIX:2008 defines
+	  for setpgid can occur.
+	*/
+#ifdef HAVE_SETPGID
+	setpgid(curpid, 0);
+#elif defined SETPGRP_HAVE_ARG
+	setpgrp(curpid, 0);
+#endif
+	
+	free_string_array(argv, argc);
+	if (own_args)
+	{
+		Py_DECREF(args);
+	}
+	return 1;
+}
+
+static inline bool attr_to_timeval(PyObject *obj, const char *attr, _PyTime_timeval *ptv)
+{
+#ifdef HAVE_LONG_LONG
+	long long i_whole;
+#else
+	long i_whole;
+#endif
+	PyObject *whole, *frac, *million, *usec, *usec_whole;
+	PyObject *member = PyObject_GetAttrString(obj, attr);
+	if (member == NULL)
+	{
+		return false;
+	}
+	if (member == Py_None)
+	{
+		Py_DECREF(member);
+		timerclear(ptv);
+		return true;
+	}
+	whole = PyNumber_Int(member);
+	if (whole == NULL)
+	{
+		Py_DECREF(member);
+		return false;
+	}
+#ifdef HAVE_LONG_LONG
+	i_whole = PyLong_AsLongLong(whole);
+#else
+	i_whole = PyInt_AsLong(whole);
+#endif
+	if (i_whole == -1 && PyErr_Occurred() != NULL)
+	{
+		Py_DECREF(whole);
+		Py_DECREF(member);
+		return false;
+	}
+	// FIXME: detect time_t overflow
+	ptv->tv_sec = i_whole;
+	frac = PyNumber_Subtract(member, whole);
+	Py_DECREF(whole);
+	Py_DECREF(member);
+	if (frac == NULL)
+	{
+		return false;
+	}
+	million = PyInt_FromLong(1000000);
+	if (million == NULL)
+	{
+		Py_DECREF(frac);
+		return false;
+	}
+	usec = PyNumber_InPlaceMultiply(frac, million);
+	Py_DECREF(million);
+	Py_DECREF(frac);
+	if (usec == NULL)
+	{
+		return false;
+	}
+	usec_whole = PyNumber_Int(usec);
+	Py_DECREF(usec);
+	if (usec_whole == NULL)
+	{
+		return false;
+	}
+	// FIXME: a sanity check (0 <= value < 1000000) here wouldn't harm
+	ptv->tv_usec = PyInt_AsLong(usec_whole);
+	Py_DECREF(usec_whole);
+	return ptv->tv_usec != -1 || PyErr_Occurred() == NULL;
+}
+
+#ifdef __cplusplus
+typedef struct { char a[2]; } two_chars;
+static char is_int(char);
+static char is_int(signed char);
+static char is_int(unsigned char);
+static char is_int(short);
+static char is_int(unsigned short);
+static char is_int(int);
+static char is_int(unsigned);
+static char is_int(long);
+static char is_int(unsigned long);
+#ifdef HAVE_LONG_LONG
+static char is_int(long long);
+static char is_int(unsigned long long);
+#endif
+static two_chars is_int(...);
+#endif
+
+static inline bool timeval_to_attr(_PyTime_timeval *ptv, PyObject *obj, const char *attr)
+{
+	PyObject *value;
+#ifdef __cplusplus
+	// If tv_sec has an integral type and !tv_usec, try to create a Python int
+	if (sizeof is_int(ptv->tv_sec) == sizeof(char) && !ptv->tv_usec)
+	{
+		if (ptv->tv_sec <= LONG_MAX)
+		{
+			value = PyInt_FromLong(ptv->tv_sec);
+		}
+		// FIXME: signed/unsigned comparisons ruin everything
+#ifdef HAVE_LONG_LONG
+		else// if (ptv->tv_sec <= ULLONG_MAX)
+		{
+			value = PyLong_FromUnsignedLongLong(ptv->tv_sec);
+		}
+#else
+//		else if (ptv->tv_sec <= ULONG_MAX)
+//		{
+//			value = PyLong_FromUnsignedLong(ptv->tv_sec);
+//		}
+//#endif
+		else
+		{
+			value = PyFloat_FromDouble(ptv->tv_sec);
+		}
+//
+#endif
+//
+	}
+	else
+#endif
+	{
+		// TODO: use decimal.Decimal or fractions.Fraction
+		value = PyFloat_FromDouble(ptv->tv_sec + ptv->tv_usec * 0.000001);
+	}
+	if (value == NULL)
+	{
+		return false;
+	}
+	if (PyObject_SetAttrString(obj, attr, value) == -1)
+	{
+		return false;
+	}
+	Py_DECREF(value);
+	return true;
+}
+
+/*
+TODO/FIXME:
+* Replace timeval->timespec and select->pselect if pselect is available
+ (preferably only if pselect is not a wrapper around select).
+* File descriptors might be >= FD_SETSIZE?
+*/
+static PyObject *_unix_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+	PyObject *testcase = NULL, *obj;
+	_unix__PopenPlaceholderObject *Popen_placeholder;
+	int spawn_errno = 0, spawn_status, s, c2ppipe[2], retstat;
+	struct child_stats stats = zero_stats;
+	_PyTime_timeval maxwalltime, maxcputime, timeout, time_start;
+	Py_ssize_t maxmemory, r;
+	size_t stats_read = 0;
+	fd_set readfds;
+	char c;
+	bool have_maxwalltime;
+	
+	if (kwds != NULL)
+	{
+		testcase = PyDict_GetItemString(kwds, "case");
+	}
+	if (testcase == NULL)
+	{
+		PyErr_SetString(PyExc_TypeError, "call() requires a keyword argument 'case'");
+		return NULL;
+	}
+	Py_INCREF(testcase);
+	PyDict_DelItemString(kwds, "case");
+	
+	if (!attr_to_timeval(testcase, "maxwalltime", &maxwalltime)
+	 || !attr_to_timeval(testcase, "maxcputime", &maxcputime))
+	{
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	
+	obj = PyObject_GetAttrString(testcase, "maxmemory");
+	if (obj == NULL)
+	{
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	if (PyObject_IsTrue(obj))
+	{
+		PyObject *factor, *bytes;
+		factor = PyInt_FromLong(1024 * 1024);
+		if (factor == NULL)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+		bytes = PyNumber_Multiply(obj, factor);
+		Py_DECREF(factor);
+		if (bytes == NULL)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+		maxmemory = PyNumber_AsSsize_t(bytes, PyExc_OverflowError);
+		Py_DECREF(bytes);
+		if (maxmemory == -1 && PyErr_Occurred() != NULL)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+	}
+	else
+	{
+		maxmemory = 0;
+	}
+	Py_DECREF(obj);
+	
+#ifdef HAVE_PIPE2
+	if (pipe2(c2ppipe, O_CLOEXEC))
+	{
+		PyErr_SetFromErrno(PyExc_IOError);
+		Py_DECREF(testcase);
+		return NULL;
+	}
+#else
+	if (pipe(c2ppipe))
+	{
+		PyErr_SetFromErrno(PyExc_IOError);
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	// Does any other thread fork/spawn right now? Please shoot it in the head
+	// (well, if this ends up causing trouble, anyway)
+	if (fcntl(c2ppipe[0], F_SETFD, fcntl(c2ppipe[0], F_GETFD) | FD_CLOEXEC) == -1
+	 || fcntl(c2ppipe[1], F_SETFD, fcntl(c2ppipe[1], F_GETFD) | FD_CLOEXEC) == -1)
+	{
+		PyErr_SetFromErrno(PyExc_IOError);
+		close(c2ppipe[0]);
+		close(c2ppipe[1]);
+		Py_DECREF(testcase);
+		return NULL;
+	}
+#endif
+	
+	spawn_status = my_spawn(args, kwds, c2ppipe, maxcputime.tv_sec + (maxcputime.tv_usec > 0), maxmemory);
+	close(c2ppipe[1]);
+	if (!spawn_status)
+	{
+		PyObject *type, *value, *traceback, *e;
+		close(c2ppipe[0]);
+		Py_DECREF(testcase);
+		PyErr_Fetch(&type, &value, &traceback);
+		PyErr_NormalizeException(&type, &value, &traceback);
+		Py_XDECREF(traceback);
+		Py_DECREF(type);
+		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
+		Py_DECREF(value);
+		PyErr_SetObject(CannotStartTestee, e);
+		Py_DECREF(e);
+		return NULL;
+	}
+	else if (spawn_status < 0)
+	{
+		close(c2ppipe[0]);
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	
+	// FIXME: use select in order not to miss SIGINT
+	while ((r = read(c2ppipe[0], &c, 1)) == -1 && errno == EINTR)
+	{
+		if (PyErr_CheckSignals() == -1)
+		{
+			PROPAGATE_SIGINT;
+			close(c2ppipe[0]);
+			Py_DECREF(testcase);
+			TERM_TESTEE;
+			return NULL;
+		}
+	}
+	if (r == 1)
+	{
+		if (c == TESTEE_SPAWNED)
+		{
+			size_t got = 0;
+			while (got < sizeof time_start)
+			{
+				r = read(c2ppipe[0], got + (char *) &time_start, sizeof time_start - got);
+				if (r > 0)
+				{
+					got += r;
+				}
+				else if (!r)
+				{
+					errno = 0;
+					PyErr_SetFromErrno(PyExc_IOError);
+					goto spawn_failed;
+				}
+				else if (errno == EINTR)
+				{
+					if (PyErr_CheckSignals() == -1)
+					{
+						PROPAGATE_SIGINT;
+						close(c2ppipe[0]);
+						Py_DECREF(testcase);
+						TERM_TESTEE;
+						return NULL;
+					}
+				}
+				else
+				{
+					PyErr_SetFromErrno(PyExc_IOError);
+					goto spawn_failed;
+				}
+			}
+			if (!timeval_to_attr(&time_start, testcase, "time_started"))
+			{
+				close(c2ppipe[0]);
+				Py_DECREF(testcase);
+				TERM_TESTEE;
+				return NULL;
+			}
+		}
+		else // if (c == TESTEE_SPAWN_FAILED)
+		{
+			size_t got = 0;
+			while (got < sizeof spawn_errno)
+			{
+				r = read(c2ppipe[0], got + (char *) &spawn_errno, sizeof spawn_errno - got);
+				if (r > 0)
+				{
+					got += r;
+				}
+				else if (!r)
+				{
+					// Can't get the real error; use zero instead
+					spawn_errno = 0;
+					break;
+				}
+				else if (errno == EINTR)
+				{
+					if (PyErr_CheckSignals() == -1)
+					{
+						PROPAGATE_SIGINT;
+						close(c2ppipe[0]);
+						Py_DECREF(testcase);
+						TERM_TESTEE;
+						return NULL;
+					}
+				}
+				else
+				{
+					PyErr_SetFromErrno(PyExc_IOError);
+					goto spawn_failed;
+				}
+			}
+			errno = spawn_errno;
+			/*
+			if (errno == EACCES || errno == EINVAL || errno == ELOOP
+			 || errno == ENAMETOOLONG || errno == ENOENT || errno == ENOTDIR
+			 || errno == ENOEXEC || errno == ETXTBSY)
+			{
+				PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PySequence_ITEM(args, 0));
+			}
+			else
+			{*/
+				PyErr_SetFromErrno(PyExc_OSError);
+			//}
+			goto spawn_failed;
+		}
+	}
+	else
+	{
+		PyObject *type, *value, *traceback, *e;
+		if (!r) errno = 0;
+		PyErr_SetFromErrno(PyExc_IOError);
+spawn_failed:
+		Py_DECREF(testcase);
+		close(c2ppipe[0]);
+		TERM_TESTEE;
+		PyErr_Fetch(&type, &value, &traceback);
+		PyErr_NormalizeException(&type, &value, &traceback);
+		Py_XDECREF(traceback);
+		Py_DECREF(type);
+		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
+		Py_DECREF(value);
+		PyErr_SetObject(CannotStartTestee, e);
+		Py_DECREF(e);
+		return NULL;
+	}
+	
+	Py_BEGIN_ALLOW_THREADS
+	timeradd(&time_start, &maxwalltime, &time_end);
+	FD_ZERO(&readfds);
+	have_maxwalltime = timerisset(&maxwalltime);
+	/*
+	Implementations may place limitations on the maximum timeout
+	interval supported. All implementations shall support a maximum
+	timeout interval of at least 31 days. If the timeout argument
+	specifies a timeout interval greater than the implementation-
+	defined maximum value, the maximum value shall be used as the
+	actual timeout value.
+		(POSIX:2008)
+	Therefore the loop and the && timercmp(&time_end, &now, <).
+	*/
+	for (;;)
+	{
+		_PyTime_timeval now;
+		int maxfd = c2ppipe[0];
+#ifdef HAVE_TERMIOS_H
+		if (catch_escape) FD_SET(0, &readfds);
+#endif
+#ifdef USE_WAKEUP_FD
+		FD_SET(intpipe[0], &readfds);
+		if (intpipe[0] > maxfd) maxfd = intpipe[0];
+#endif
+		FD_SET(c2ppipe[0], &readfds);
+		
+		if (have_maxwalltime)
+		{
+			_PyTime_gettimeofday(&now);
+			if (timercmp(&time_end, &now, <))
+			{
+				timerclear(&timeout);
+			}
+			else
+			{
+				timersub(&time_end, &now, &timeout);
+			}
+			
+			s = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
+			
+			if (!s && timercmp(&time_end, &now, <))
+			{
+				close(c2ppipe[0]);
+				TERM_TESTEE;
+				Py_BLOCK_THREADS
+				Py_DECREF(testcase);
+				PyErr_SetObject(WallTimeLimitExceeded, NULL);
+				return NULL;
+			}
+		}
+		else
+		{
+			s = select(maxfd + 1, &readfds, NULL, NULL, NULL);
+		}
+		
+		if (s < 0 && errno == EINTR)
+		{
+			Py_BLOCK_THREADS
+			if (PyErr_CheckSignals() == -1)
+			{
+				PROPAGATE_SIGINT;
+				close(c2ppipe[0]);
+				Py_DECREF(testcase);
+				TERM_TESTEE;
+				return NULL;
+			}
+			Py_UNBLOCK_THREADS
+		}
+		else if (s < 0 && errno != EAGAIN)
+		{
+			Py_BLOCK_THREADS
+			PyErr_SetFromErrno(PyExc_IOError);
+			close(c2ppipe[0]);
+			Py_DECREF(testcase);
+			TERM_TESTEE;
+			return NULL;
+		}
+#ifdef USE_WAKEUP_FD
+		else if (s > 0 && FD_ISSET(intpipe[0], &readfds))
+		{
+			// FIXME: is error handling needed?
+			while (read(intpipe[0], dont_care_buffer, sizeof dont_care_buffer) > 0);
+			Py_BLOCK_THREADS
+			if (PyErr_CheckSignals() == -1)
+			{
+				PROPAGATE_SIGINT;
+				close(c2ppipe[0]);
+				Py_DECREF(testcase);
+				TERM_TESTEE;
+				return NULL;
+			}
+			Py_UNBLOCK_THREADS
+		}
+#endif
+#ifdef HAVE_TERMIOS_H
+		else if (s > 0 && !FD_ISSET(c2ppipe[0], &readfds))
+		{
+			// FIXME: is error and EOF handling needed?
+			if ((r = read(0, &c, 1)) == 1)
+			{
+				if (c == '\33')
+				{
+					close(c2ppipe[0]);
+					TERM_TESTEE;
+					Py_BLOCK_THREADS
+					Py_DECREF(testcase);
+					PyErr_SetObject(CanceledByUser, NULL);
+					return NULL;
+				}
+			}
+			else if (r == -1 && errno == EINTR)
+			{
+				if (PyErr_CheckSignals() == -1)
+				{
+					PROPAGATE_SIGINT;
+					close(c2ppipe[0]);
+					Py_DECREF(testcase);
+					TERM_TESTEE;
+					return NULL;
+				}
+			}
+		}
+#endif
+		else if (s > 0)
+		{
+			bool blocked_threads = false;
+			while ((r = read(c2ppipe[0], stats_read + (char *) &stats, sizeof stats - stats_read)) == -1 && errno == EINTR)
+			{
+				Py_BLOCK_THREADS
+				blocked_threads = true;
+				if (PyErr_CheckSignals() == -1)
+				{
+					PROPAGATE_SIGINT;
+					close(c2ppipe[0]);
+					Py_DECREF(testcase);
+					TERM_TESTEE;
+					return NULL;
+				}
+			}
+			if (r > 0)
+			{
+				stats_read += r;
+			}
+			else if (!r)
+			{
+				break;
+			}
+			else
+			{
+				close(c2ppipe[0]);
+				TERM_TESTEE;
+				if (!blocked_threads)
+				{
+					Py_BLOCK_THREADS
+				}
+				Py_DECREF(testcase);
+				PyErr_SetFromErrno(PyExc_IOError);
+				return NULL;
+			}
+			if (blocked_threads)
+			{
+				Py_UNBLOCK_THREADS
+			}
+		}
+	}
+	close(c2ppipe[0]);
+	Py_END_ALLOW_THREADS
+	
+#ifdef HAVE_WAITPID
+	while (waitpid(curpid, &retstat, 0) != curpid)
+#else
+	while (wait(&retstat) != curpid)
+#endif
+	{
+		if (PyErr_CheckSignals() == -1)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+	}
+	
+	if (WIFEXITED(retstat) && WEXITSTATUS(retstat) == 127)
+	{
+		PyObject *type, *value, *traceback, *e;
+		Py_DECREF(testcase);
+		errno = 0;
+		PyErr_SetFromErrno(PyExc_OSError);
+		PyErr_Fetch(&type, &value, &traceback);
+		PyErr_NormalizeException(&type, &value, &traceback);
+		Py_XDECREF(traceback);
+		Py_DECREF(type);
+		e = PyObject_CallFunctionObjArgs(CannotStartTestee, value, NULL);
+		Py_DECREF(value);
+		PyErr_SetObject(CannotStartTestee, e);
+		Py_DECREF(e);
+		return NULL;
+	}
+	else if (!WIFEXITED(retstat) || WEXITSTATUS(retstat))
+	{
+		Py_DECREF(testcase);
+		if (WIFSTOPPED(retstat))
+		{
+			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: stopped by signal %d", WSTOPSIG(retstat));
+		}
+		else if (WIFSIGNALED(retstat))
+		{
+			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: terminated by signal %d", WTERMSIG(retstat));
+		}
+		else if (WIFEXITED(retstat))
+		{
+			return PyErr_Format(PyExc_EnvironmentError, "unexpected exit status from worker: %d", WEXITSTATUS(retstat));
+		}
+		else
+		{
+			PyErr_SetString(PyExc_EnvironmentError, "unexpected exit status from worker: not exited, signaled or stopped");
+			return NULL;
+		}
+	}
+	
+	if (stats_read != sizeof stats)
+	{
+		Py_DECREF(testcase);
+		PyErr_SetString(PyExc_EnvironmentError, "unexpectedly early end of output from worker");
+		return NULL;
+	}
+	
+	if (timerisset(&maxwalltime) && timercmp(&stats.walltime, &maxwalltime, >))
+	{
+		Py_DECREF(testcase);
+		PyErr_SetObject(WallTimeLimitExceeded, NULL);
+		return NULL;
+	}
+	
+	obj = PyInt_FromLong(0);
+	if (obj == NULL)
+	{
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	if (PyObject_SetAttrString(testcase, "time_started", obj) == -1)
+	{
+		Py_DECREF(testcase);
+		return NULL;
+	}
+	Py_DECREF(obj);
+	
+#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
+	if (timerisset(&maxcputime) || !timerisset(&maxwalltime))
+	{
+		PyObject *cputls;
+		if (!timeval_to_attr(&stats.cputime, testcase, "time_stopped"))
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+		cputls = PyObject_GetAttrString(testcase, "cpu_time_limit_string");
+		if (cputls == NULL)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+		if (PyObject_SetAttrString(testcase, "time_limit_string", cputls) == -1)
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+		Py_DECREF(cputls);
+		if (timerisset(&maxcputime) && timercmp(&stats.cputime, &maxcputime, >))
+		{
+			Py_DECREF(testcase);
+			PyErr_SetObject(CPUTimeLimitExceeded, NULL);
+			return NULL;
+		}
+	}
+	else
+#endif
+	{
+		if (!timeval_to_attr(&stats.walltime, testcase, "time_stopped"))
+		{
+			Py_DECREF(testcase);
+			return NULL;
+		}
+	}
+	
+#if defined HAVE_SYS_RESOURCE_H || defined HAVE_WAIT4 || defined HAVE_WAIT3
+	if (maxmemory && stats.memory > maxmemory)
+	{
+		Py_DECREF(testcase);
+		PyErr_SetObject(MemoryLimitExceeded, NULL);
+		return NULL;
+	}
+#endif
+	
+	Popen_placeholder = PyObject_New(_unix__PopenPlaceholderObject, &_unix__PopenPlaceholderType);
+	if (Popen_placeholder == NULL)
+	{
+		return NULL;
+	}
+	Popen_placeholder->returncode = stats.returncode;
+	PyObject_SetAttrString(testcase, "process", (PyObject *) Popen_placeholder);
+	Py_DECREF(Popen_placeholder);
+	Py_DECREF(testcase);
+	Py_RETURN_NONE;
+}
+
+static PyObject *_unix_pause(PyObject *self)
+{
+#ifdef HAVE_TERMIOS_H
+	if (catch_escape)
+	{
+		char c;
+		while (read(0, &c, 1) == -1 && errno == EINTR);
+	}
+#endif
+	Py_RETURN_NONE;
+}
+
+static PyMethodDef _unixMethods[] =
+{
+	{ "call", (PyCFunction) _unix_call, METH_VARARGS | METH_KEYWORDS, "Call a process." },
+	{ "pause", (PyCFunction) _unix_pause, METH_NOARGS, "Block until a key is pressed." },
+	{ NULL }
+};
+
+#ifdef USE_WAKEUP_FD
+static void close_intpipe(void)
+{
+	close(intpipe[0]);
+	close(intpipe[1]);
+	intpipe[0] = intpipe[1] = 0;
+}
+#endif
+
+#ifdef HAVE_TERMIOS_H
+static void restore_termios(void)
+{
+	tcsetattr(0, TCSAFLUSH, &orig_termios);
+#ifdef USE_WAKEUP_FD
+	close_intpipe();
+#endif
+}
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+#define INIT_FAIL return NULL
+static PyModuleDef _unixmodule =
+{
+	PyModuleDef_HEAD_INIT,
+	"_unix",
+	NULL,
+	-1,
+	_unixMethods
+};
+
+PyMODINIT_FUNC PyInit__unix(void)
+#else
+#define INIT_FAIL return
+PyMODINIT_FUNC init_unix(void)
+#endif
+{
+	struct termios new_termios;
+	PyObject *exceptions;
+	
+	_unix__PopenPlaceholderType.tp_new = PyType_GenericNew;
+	if (PyType_Ready(&_unix__PopenPlaceholderType) == -1)
+	{
+		INIT_FAIL;
+	}
+	
+	exceptions = PyImport_ImportModule("upreckon.exceptions");
+	if (exceptions == NULL
+	 || (CannotStartTestee = PyObject_GetAttrString(exceptions, "CannotStartTestee")) == NULL
+	 || (CanceledByUser = PyObject_GetAttrString(exceptions, "CanceledByUser")) == NULL
+	 || (WallTimeLimitExceeded = PyObject_GetAttrString(exceptions, "WallTimeLimitExceeded")) == NULL
+	 || (CPUTimeLimitExceeded = PyObject_GetAttrString(exceptions, "CPUTimeLimitExceeded")) == NULL
+	 || (MemoryLimitExceeded = PyObject_GetAttrString(exceptions, "MemoryLimitExceeded")) == NULL)
+	{
+		Py_XDECREF(MemoryLimitExceeded);
+		Py_XDECREF(CPUTimeLimitExceeded);
+		Py_XDECREF(WallTimeLimitExceeded);
+		Py_XDECREF(CanceledByUser);
+		Py_XDECREF(CannotStartTestee);
+		Py_XDECREF(exceptions);
+		INIT_FAIL;
+	}
+	Py_DECREF(exceptions);
+	
+#ifdef WITH_NEXT_FRAMEWORK
+	if (environ == NULL)
+	{
+		environ = *_NSGetEnviron();
+	}
+#endif
+	
+#ifdef USE_WAKEUP_FD
+	if (!intpipe[0] || !intpipe[1])
+	{
+#ifdef HAVE_PIPE2
+		if (pipe2(intpipe, O_CLOEXEC | O_NONBLOCK))
+		{
+			PyErr_SetFromErrno(PyExc_IOError);
+			Py_DECREF(MemoryLimitExceeded);
+			Py_DECREF(CPUTimeLimitExceeded);
+			Py_DECREF(WallTimeLimitExceeded);
+			Py_DECREF(CanceledByUser);
+			Py_DECREF(CannotStartTestee);
+			INIT_FAIL;
+		}
+#else
+		if (pipe(intpipe))
+		{
+			PyErr_SetFromErrno(PyExc_IOError);
+			Py_DECREF(MemoryLimitExceeded);
+			Py_DECREF(CPUTimeLimitExceeded);
+			Py_DECREF(WallTimeLimitExceeded);
+			Py_DECREF(CanceledByUser);
+			Py_DECREF(CannotStartTestee);
+			INIT_FAIL;
+		}
+		// Other threads must not fork now
+		if (fcntl(intpipe[0], F_SETFD, fcntl(intpipe[0], F_GETFD) | FD_CLOEXEC) == -1
+		 || fcntl(intpipe[1], F_SETFD, fcntl(intpipe[1], F_GETFD) | FD_CLOEXEC) == -1
+		 || fcntl(intpipe[0], F_SETFL, fcntl(intpipe[0], F_GETFL) | O_NONBLOCK) == -1
+		 || fcntl(intpipe[1], F_SETFL, fcntl(intpipe[1], F_GETFL) | O_NONBLOCK) == -1)
+		{
+			PyErr_SetFromErrno(PyExc_IOError);
+			close(intpipe[0]);
+			close(intpipe[1]);
+			Py_DECREF(MemoryLimitExceeded);
+			Py_DECREF(CPUTimeLimitExceeded);
+			Py_DECREF(WallTimeLimitExceeded);
+			Py_DECREF(CanceledByUser);
+			Py_DECREF(CannotStartTestee);
+			INIT_FAIL;
+		}
+#endif
+	}
+	
+	PySignal_SetWakeupFd(intpipe[1]);
+#endif
+	
+#ifdef HAVE_TERMIOS_H
+	if (!tcgetattr(0, &orig_termios))
+	{
+		new_termios = orig_termios;
+		// Stolen from tty.py of Python 2.7.1
+		new_termios.c_lflag &= ~(ECHO | ICANON);
+		new_termios.c_cc[VMIN] = 1;
+		new_termios.c_cc[VTIME] = 0;
+		if (!Py_AtExit(restore_termios) && !tcsetattr(0, TCSAFLUSH, &new_termios))
+		{
+			catch_escape = true;
+		}
+	}
+#ifdef USE_WAKEUP_FD
+	else
+	{
+		Py_AtExit(close_intpipe);
+	}
+#endif
+#elif defined USE_WAKEUP_FD
+	Py_AtExit(close_intpipe);
+#endif
+	
+	PyObject *module;
+#if PY_MAJOR_VERSION >= 3
+	module = PyModule_Create(&_unixmodule);
+#else
+	module = Py_InitModule("_unix", _unixMethods);
+#endif
+	if (module == NULL)
+	{
+		Py_DECREF(MemoryLimitExceeded);
+		Py_DECREF(CPUTimeLimitExceeded);
+		Py_DECREF(WallTimeLimitExceeded);
+		Py_DECREF(CanceledByUser);
+		Py_DECREF(CannotStartTestee);
+	}
+#if PY_MAJOR_VERSION >= 3
+	return module;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/compat.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,235 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+# A compatibility layer for Python 2.5+. This is what lets test.py
+# run on all versions of Python starting with 2.5, including Python 3.
+
+# A few notes regarding some compatibility-driven peculiarities
+# in the use of the language that can be seen in all modules:
+#
+# * Except statements never specify target; instead, when needed,
+#   the exception is taken from sys.exc_info(). Blame the incompatible
+#   syntaxes of the except clause in Python 2.5 and Python 3
+#   and the lack of preprocessor macros in Python of any version ;P.
+#
+# * Keyword-only parameters are never used, even for parameters
+#   that should never be given in as arguments. The reason is
+#   the laziness of some Python developers who have failed to finish
+#   implementing them in Python 2 even though they had several years
+#   of time and multiple version releases to sneak them in.
+#
+# * Abstract classes are only implemented for Python 2.6 and 2.7.
+#   ABC's require the abc module and the specification of metaclasses,
+#   but in Python 2.5, the abc module does not exist, while in Python 3,
+#   metaclasses are specified using a syntax totally incompatible
+#   with Python 2 and not usable conditionally via exec() and such
+#   because it is a detail of the syntax of the class statement itself.
+
+# Some code was adapted from Python 2.7.1 and its documentation.
+# This code is clearly marked as such in preceding comments and is
+# covered by copyright as follows:
+#
+# Copyright (c) 2001-2010 Python Software Foundation; all rights reserved.
+#
+# The code is used according to the PSF License Agreement
+# for Python 2.7.1, whose full text is available from your local
+# installation of Python (enter 'license()' in the interactive
+# interpreter) or from the Web at the following URL:
+#
+# http://docs.python.org/2.7.1/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
+
+try:
+	import builtins
+except ImportError:
+	import __builtin__ as builtins
+
+pseudobuiltins = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'next',
+                  'items', 'keys', 'values', 'zip_longest', 'callable', 'ceil')
+__all__ = pseudobuiltins + ('ABCMeta', 'abstractmethod', 'CompatBuiltins')
+
+try:
+	# Python 3
+	exec('say = print')
+except SyntaxError:
+	try:
+		# Python 2.6/2.7
+		# An alternative is exec('from __future__ import print_function; say = print');
+		# if problems arise with the current line, one should try replacing it
+		# with this one with the future import before abandoning the idea altogether
+		say = getattr(builtins, 'print')
+	except Exception:
+		# Python 2.5
+		import sys
+		# This should fully emulate the print function of Python 2.6 in Python 2.3+
+		# The error messages are taken from Python 2.6
+		# The name bindings at the bottom of this file are in effect
+		def saytypeerror(value, name):
+			return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
+		def say(*values, **kwargs):
+			sep  = kwargs.pop('sep' , None)
+			end  = kwargs.pop('end' , None)
+			file = kwargs.pop('file', None)
+			if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
+			if sep  is None: sep  = ' '
+			if end  is None: end  = '\n'
+			if file is None: file = sys.stdout
+			if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
+			if not isinstance(end, basestring): raise saytypeerror(end, 'end')
+			file.write(sep.join(map(str, values)) + end)
+
+try:
+	from os.path import relpath
+except ImportError:
+	# Python 2.5
+	import os.path as _path
+	
+	# Adapted from Python 2.7.1
+	
+	if hasattr(_path, 'splitunc'):
+		def _abspath_split(path):
+			abs = _path.abspath(_path.normpath(path))
+			prefix, rest = _path.splitunc(abs)
+			is_unc = bool(prefix)
+			if not is_unc:
+				prefix, rest = _path.splitdrive(abs)
+			return is_unc, prefix, [x for x in rest.split(_path.sep) if x]
+	else:
+		def _abspath_split(path):
+			prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path)))
+			return False, prefix, [x for x in rest.split(_path.sep) if x]
+	
+	def relpath(path, start=_path.curdir):
+		"""Return a relative version of a path"""
+		
+		if not path:
+			raise ValueError("no path specified")
+		
+		start_is_unc, start_prefix, start_list = _abspath_split(start)
+		path_is_unc, path_prefix, path_list = _abspath_split(path)
+		
+		if path_is_unc ^ start_is_unc:
+			raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
+			                                                   % (path, start))
+		if path_prefix.lower() != start_prefix.lower():
+			if path_is_unc:
+				raise ValueError("path is on UNC root %s, start on UNC root %s"
+				                                 % (path_prefix, start_prefix))
+			else:
+				raise ValueError("path is on drive %s, start on drive %s"
+				                                 % (path_prefix, start_prefix))
+		# Work out how much of the filepath is shared by start and path.
+		i = 0
+		for e1, e2 in zip(start_list, path_list):
+			if e1.lower() != e2.lower():
+				break
+			i += 1
+		
+		rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:]
+		if not rel_list:
+			return _path.curdir
+		return _path.join(*rel_list)
+	
+	_path.relpath = relpath
+
+try:
+	from abc import ABCMeta, abstractmethod
+except ImportError:
+	ABCMeta, abstractmethod = None, lambda x: x
+
+try:
+	basestring = basestring
+except NameError:
+	basestring = str
+
+# xrange is set to support simple testconf.py's written for test.py 1.x
+try:
+	xrange = range = xrange
+except NameError:
+	xrange = range = range
+
+try:
+	callable = callable
+except NameError:
+	from collections import Callable
+	callable = lambda obj: isinstance(obj, Callable)
+
+try:
+	next = next
+except NameError:
+	next = lambda obj: obj.next()
+
+try:
+	from itertools import imap as map
+except ImportError:
+	map = map
+
+try:
+	from itertools import izip as zip
+except ImportError:
+	zip = zip
+
+try:
+	from itertools import ifilter as filter
+except ImportError:
+	filter = filter
+
+try:
+	items = dict.iteritems
+except AttributeError:
+	items = dict.items
+
+try:
+	keys = dict.iterkeys
+except AttributeError:
+	keys = dict.keys
+
+try:
+	values = dict.itervalues
+except AttributeError:
+	values = dict.values
+
+from math import ceil
+if not isinstance(ceil(0), int):
+	def ceil(x):
+		y = int(x)
+		if y < x: y += 1
+		return y
+
+try:
+	# Python 3
+	from itertools import zip_longest
+except ImportError:
+	try:
+		# Python 2.6/2.7
+		from itertools import izip_longest as zip_longest
+	except ImportError:
+		# Python 2.5
+		from itertools import chain, repeat
+		# Adapted from the documentation of itertools.izip_longest
+		def zip_longest(*args, **kwargs):
+			fillvalue = kwargs.get('fillvalue')
+			def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
+				yield counter()
+			fillers = repeat(fillvalue)
+			iters = [chain(it, sentinel(), fillers) for it in args]
+			try:
+				for tup in zip(*iters):
+					yield tup
+			except IndexError:
+				pass
+
+# Automatically import * from this module into testconf.py's
+class CompatBuiltins(object):
+	__slots__ = 'originals'
+	globals = globals()
+	def __enter__(self):
+		self.originals = {}
+		for name in pseudobuiltins:
+			try:
+				self.originals[name] = getattr(builtins, name)
+			except AttributeError:
+				pass
+			setattr(builtins, name, self.globals[name])
+		return self
+	def __exit__(self, exc_type, exc_val, exc_tb):
+		for name in self.originals:
+			setattr(builtins, name, self.originals[name])
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/config.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,212 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+
+from .compat import *
+from . import files
+from __main__ import options
+
+if files.ZipArchive:
+	try:
+		import zipimport
+	except ImportError:
+		zipimport = None
+else:
+	zipimport = None
+
+import imp, os, sys, tempfile
+
+__all__ = 'load_problem', 'load_global', 'globalconf'
+
+defaults_problem = {'kind': 'batch',
+                    'usegroups': False,
+                    'maxcputime': None,
+                    'maxwalltime': None,
+                    'maxmemory': None,
+                    'dummies': (),
+                    'testsexcluded': (),
+                    'padtests': 0,
+                    'paddummies': 0,
+                    'taskweight': 100,
+                    'groupweight': {},
+                    'pointmap': {},
+                    'stdio': False,
+                    'dummyinname': '',
+                    'dummyoutname': '',
+                    'tester': None,
+                    'maxexitcode': 0,
+                    'inname': '',
+                    'ansname': ''}
+defaults_global = {'problems': None,
+                   'force_zero_exitcode': True}
+defaults_noerase = {'inname': '%.in',
+                    'outname': '%.out',
+                    'ansname': '%.ans'}
+patterns = ('inname', 'outname', 'ansname', 'testcaseinname',
+            'testcaseoutname', 'dummyinname', 'dummyoutname')
+
+class Config(object):
+	__slots__ = 'modules', '__dict__'
+	
+	def __init__(self, *modules):
+		self.modules = modules
+	
+	def __getattr__(self, name):
+		for module in self.modules:
+			try:
+				return getattr(module, name)
+			except AttributeError:
+				pass
+		# TODO: provide a message
+		raise AttributeError(name)
+
+# A helper context manager
+class ReadDeleting(object):
+	__slots__ = 'name', 'file'
+	
+	def __init__(self, name):
+		self.name = name
+	
+	def __enter__(self):
+		try:
+			self.file = open(self.name, 'rU')
+			return self.file
+		except:
+			try:
+				self.__exit__(None, None, None)
+			except:
+				pass
+			raise
+	
+	def __exit__(self, exc_type, exc_val, exc_tb):
+		self.file.close()
+		os.remove(self.name)
+
+def load_problem(problem_name):
+	global builtins
+	try:
+		dwb = sys.dont_write_bytecode
+		sys.dont_write_bytecode = True
+	except AttributeError:
+		pass
+	metafile = files.File('/'.join((problem_name, 'testconf.py')), True, 'configuration')
+	module = None
+	with CompatBuiltins() as builtins:
+		if zipimport and isinstance(metafile.archive, files.ZipArchive):
+			try:
+				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
+			except zipimport.ZipImportError:
+				pass
+			else:
+				del sys.modules['testconf']
+		if not module:
+			try:
+				with metafile.open() as f:
+					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
+			# Handle the case when f is not a true file object but imp requires one
+			except ValueError:
+				# FIXME: 2.5 lacks the delete parameter
+				with tempfile.NamedTemporaryFile(delete=False) as f:
+					inputdatafname = f.name
+				metafile.copy(inputdatafname)
+				with ReadDeleting(inputdatafname) as f:
+					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
+			del sys.modules['testconf']
+	module = Config(module, globalconf)
+	if hasattr(module, 'padwithzeroestolength'):
+		if not hasattr(module, 'padtests'):
+			try:
+				module.padtests = module.padwithzeroestolength[0]
+			except TypeError:
+				module.padtests = module.padwithzeroestolength
+		if not hasattr(module, 'paddummies'):
+			try:
+				module.paddummies = module.padwithzeroestolength[1]
+			except TypeError:
+				module.paddummies = module.padwithzeroestolength
+	if (not hasattr(module, 'maxcputime') and
+	    not hasattr(module, 'maxwalltime') and
+	    hasattr(module, 'maxtime')):
+		module.maxcputime = module.maxtime
+	for name in defaults_problem:
+		setattr(module, name, getattr(module, name, defaults_problem[name]))
+	if not module.dummyinname:
+		module.dummyinname = getattr(module, 'testcaseinname', module.dummyinname)
+	if not module.dummyoutname:
+		module.dummyoutname = getattr(module, 'testcaseoutname', module.dummyoutname)
+	if not hasattr(module, 'path'):
+		if hasattr(module, 'name'):
+			module.path = module.name
+		elif sys.platform != 'win32':
+			module.path = os.path.join(os.path.curdir, problem_name)
+		else:
+			module.path = problem_name
+	for name in 'pointmap', 'groupweight':
+		oldmap = getattr(module, name)
+		if isinstance(oldmap, dict):
+			newmap = {}
+			for key in oldmap:
+				if not options.legacy and isinstance(key, basestring):
+					newmap[key] = oldmap[key]
+				else:
+					try:
+						for k in key:
+							newmap[k] = oldmap[key]
+					except TypeError:
+						newmap[key] = oldmap[key]
+			setattr(module, name, newmap)
+	if options.no_maxtime:
+		module.maxcputime = module.maxwalltime = 0
+	try:
+		sys.dont_write_bytecode = dwb
+	except NameError:
+		pass
+	for name in patterns:
+		if hasattr(module, name):
+			setattr(module, name, getattr(module, name).replace('%', problem_name))
+	return module
+
+def load_global():
+	global builtins
+	try:
+		dwb = sys.dont_write_bytecode
+		sys.dont_write_bytecode = True
+	except AttributeError:
+		pass
+	metafile = files.File('testconf.py', True, 'configuration')
+	module = None
+	with CompatBuiltins() as builtins:
+		if zipimport and isinstance(metafile.archive, files.ZipArchive):
+			try:
+				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
+			except zipimport.ZipImportError:
+				pass
+			else:
+				del sys.modules['testconf']
+		if not module:
+			try:
+				with metafile.open() as f:
+					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
+			# Handle the case when f is not a true file object but imp requires one
+			except ValueError:
+				# FIXME: 2.5 lacks the delete parameter
+				with tempfile.NamedTemporaryFile(delete=False) as f:
+					inputdatafname = f.name
+				metafile.copy(inputdatafname)
+				with ReadDeleting(inputdatafname) as f:
+					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
+			del sys.modules['testconf']
+	for name in defaults_global:
+		setattr(module, name, getattr(module, name, defaults_global[name]))
+	if not options.erase:
+		for name in defaults_noerase:
+			setattr(module, name, getattr(module, name, defaults_noerase[name]))
+	if hasattr(module, 'tasknames'):
+		module.problems = module.tasknames
+	global globalconf
+	globalconf = module
+	try:
+		sys.dont_write_bytecode = dwb
+	except NameError:
+		pass
+	return module
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/exceptions.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,37 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+__all__ = ('TestCaseNotPassed', 'TestCaseSkipped', 'TimeLimitExceeded',
+           'CPUTimeLimitExceeded', 'WallTimeLimitExceeded',
+           'MemoryLimitExceeded', 'CanceledByUser', 'WrongAnswer',
+           'NonZeroExitCode', 'ExceptionWrapper', 'CannotStartTestee',
+           'CannotStartValidator', 'CannotReadOutputFile',
+           'CannotReadInputFile', 'CannotReadAnswerFile')
+
+class TestCaseNotPassed(Exception): __slots__ = ()
+class TestCaseSkipped(TestCaseNotPassed): __slots__ = ()
+class TimeLimitExceeded(TestCaseNotPassed): __slots__ = ()
+class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
+class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = ()
+class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = ()
+class CanceledByUser(TestCaseNotPassed): __slots__ = ()
+
+class WrongAnswer(TestCaseNotPassed):
+	__slots__ = 'comment'
+	def __init__(self, comment=''):
+		self.comment = comment
+
+class NonZeroExitCode(TestCaseNotPassed):
+	__slots__ = 'exitcode'
+	def __init__(self, exitcode):
+		self.exitcode = exitcode
+
+class ExceptionWrapper(TestCaseNotPassed):
+	__slots__ = 'upstream'
+	def __init__(self, upstream):
+		self.upstream = upstream
+
+class CannotStartTestee(ExceptionWrapper): __slots__ = ()
+class CannotStartValidator(ExceptionWrapper): __slots__ = ()
+class CannotReadOutputFile(ExceptionWrapper): __slots__ = ()
+class CannotReadInputFile(ExceptionWrapper): __slots__ = ()
+class CannotReadAnswerFile(ExceptionWrapper): __slots__ = ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/files.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,241 @@
+# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>
+
+"""File access routines and classes with support for archives."""
+
+from __future__ import division, with_statement
+
+from .compat import *
+import contextlib, os, shutil, sys
+
+# You don't need to know about anything else.
+__all__ = 'File',
+
+# In these two variables, use full stops no matter what os.extsep is;
+# all full stops will be converted to os.extsep on the fly
+archives = 'tests.tar', 'tests.zip', 'tests.tgz', 'tests.tar.gz', 'tests.tbz2', 'tests.tar.bz2'
+formats = {}
+
+class Archive(object):
+	__slots__ = 'file'
+	
+	if ABCMeta:
+		__metaclass__ = ABCMeta
+	
+	def __new__(cls, path):
+		"""
+		Create a new instance of the archive class corresponding
+		to the file name in the given path.
+		"""
+		if cls is not Archive:
+			return object.__new__(cls)
+		else:
+			# Do this by hand rather than through os.path.splitext
+			# because we support multi-dotted file name extensions
+			ext = path.partition(os.path.extsep)[2]
+			while ext:
+				if ext in formats:
+					return formats[ext](path)
+				ext = ext.partition(os.path.extsep)[2]
+			raise LookupError("unsupported archive file name extension in file name '%s'" % filename)
+	
+	@abstractmethod
+	def __init__(self, path): raise NotImplementedError
+	
+	@abstractmethod
+	def extract(self, name, target): raise NotImplementedError
+
+try:
+	import tarfile
+except ImportError:
+	TarArchive = None
+else:
+	class TarArchive(Archive):
+		__slots__ = '_namelist'
+		
+		def __init__(self, path):
+			self.file = tarfile.open(path)
+		
+		def extract(self, name, target):
+			member = self.file.getmember(name)
+			member.name = target
+			self.file.extract(member)
+		
+		# TODO: somehow automagically emulate universal line break support
+		def open(self, name):
+			return self.file.extractfile(name)
+		
+		def exists(self, queried_name):
+			if not hasattr(self, '_namelist'):
+				names = set()
+				for name in self.file.getnames():
+					cutname = name
+					while cutname:
+						names.add(cutname)
+						cutname = cutname.rpartition('/')[0]
+				self._namelist = frozenset(names)
+			return queried_name in self._namelist
+		
+		def __enter__(self):
+			if hasattr(self.file, '__enter__'):
+				self.file.__enter__()
+			return self
+		
+		def __exit__(self, exc_type, exc_value, traceback):
+			if hasattr(self.file, '__exit__'):
+				return self.file.__exit__(exc_type, exc_value, traceback)
+			elif exc_type is None:
+				self.file.close()
+			else:
+				# This code was shamelessly copied from tarfile.py of Python 2.7
+				if not self.file._extfileobj:
+					self.file.fileobj.close()
+				self.file.closed = True
+	
+	formats['tar'] = formats['tgz'] = formats['tar.gz'] = formats['tbz2'] = formats['tar.bz2'] = TarArchive
+
+try:
+	import zipfile
+except ImportError:
+	ZipArchive = None
+else:
+	class ZipArchive(Archive):
+		__slots__ = '_namelist'
+		
+		def __init__(self, path):
+			self.file = zipfile.ZipFile(path)
+		
+		def extract(self, name, target):
+			member = self.file.getinfo(name)
+			# FIXME: 2.5 lacks ZipFile.extract
+			if os.path.isabs(target):
+				# To my knowledge, this is as portable as it gets
+				path = os.path.join(os.path.splitdrive(target)[0], os.path.sep)
+				member.filename = os.path.relpath(target, path)
+				self.file.extract(member, path)
+			else:
+				member.filename = os.path.relpath(target)
+				self.file.extract(member)
+		
+		def open(self, name):
+			return self.file.open(name, 'rU')
+		
+		def exists(self, queried_name):
+			if not hasattr(self, '_namelist'):
+				names = set()
+				for name in self.file.namelist():
+					cutname = name
+					while cutname:
+						names.add(cutname)
+						cutname = cutname.rpartition('/')[0]
+				self._namelist = frozenset(names)
+			return queried_name in self._namelist
+		
+		def __enter__(self):
+			if hasattr(self.file, '__enter__'):
+				self.file.__enter__()
+			return self
+		
+		def __exit__(self, exc_type, exc_value, traceback):
+			if hasattr(self.file, '__exit__'):
+				return self.file.__exit__(exc_type, exc_value, traceback)
+			else:
+				return self.file.close()
+	
+	formats['zip'] = ZipArchive
+
+# Remove unsupported archive formats and replace full stops
+# with the platform-dependent file name extension separator
+def issupported(filename, formats=formats):
+	ext = filename.partition('.')[2]
+	while ext:
+		if ext in formats: return True
+		ext = ext.partition('.')[2]
+	return False
+archives = [filename.replace('.', os.path.extsep) for filename in filter(issupported, archives)]
+formats = dict((item[0].replace('.', os.path.extsep), item[1]) for item in items(formats))
+
+open_archives = {}
+
+def open_archive(path):
+	if path in open_archives:
+		return open_archives[path]
+	else:
+		open_archives[path] = archive = Archive(path)
+		return archive
+
+class File(object):
+	__slots__ = 'virtual_path', 'real_path', 'full_real_path', 'archive'
+	
+	def __init__(self, virtpath, allow_root=False, msg='test data'):
+		self.virtual_path = virtpath
+		self.archive = None
+		if not self.realize_path('', tuple(comp.replace('.', os.path.extsep) for comp in virtpath.split('/')), allow_root):
+			raise IOError("%s file '%s' could not be found" % (msg, virtpath))
+	
+	def realize_path(self, root, virtpath, allow_root=False, hastests=False):
+		if root and not os.path.exists(root):
+			return False
+		if len(virtpath) > 1:
+			if self.realize_path(os.path.join(root, virtpath[0]), virtpath[1:], allow_root, hastests):
+				return True
+			elif not hastests:
+				if self.realize_path(os.path.join(root, 'tests'), virtpath, allow_root, True):
+					return True
+				for archive in archives:
+					path = os.path.join(root, archive)
+					if os.path.exists(path):
+						if self.realize_path_archive(open_archive(path), '', virtpath, path):
+							return True
+			if self.realize_path(root, virtpath[1:], allow_root, hastests):
+				return True
+		else:
+			if not hastests:
+				path = os.path.join(root, 'tests', virtpath[0])
+				if os.path.exists(path):
+					self.full_real_path = self.real_path = path
+					return True
+				for archive in archives:
+					path = os.path.join(root, archive)
+					if os.path.exists(path):
+						if self.realize_path_archive(open_archive(path), '', virtpath, path):
+							return True
+			if hastests or allow_root:
+				path = os.path.join(root, virtpath[0])
+				if os.path.exists(path):
+					self.full_real_path = self.real_path = path
+					return True
+		return False
+	
+	def realize_path_archive(self, archive, root, virtpath, archpath):
+		if root and not archive.exists(root):
+			return False
+		if root: path = ''.join((root, '/', virtpath[0]))
+		else: path = virtpath[0]
+		if len(virtpath) > 1:
+			if self.realize_path_archive(archive, path, virtpath[1:], archpath):
+				return True
+			elif self.realize_path_archive(archive, root, virtpath[1:], archpath):
+				return True
+		else:
+			if archive.exists(path):
+				self.archive = archive
+				self.real_path = path
+				self.full_real_path = os.path.join(archpath, *path.split('/'))
+				return True
+		return False
+	
+	def open(self):
+		if self.archive:
+			file = self.archive.open(self.real_path)
+			if hasattr(file, '__exit__'):
+				return file
+			else:
+				return contextlib.closing(file)
+		else:
+			return open(self.real_path, 'rU')
+	
+	def copy(self, target):
+		if self.archive:
+			self.archive.extract(self.real_path, target)
+		else:
+			shutil.copy(self.real_path, target)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/problem.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,337 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+
+from .compat import *
+from .exceptions import *
+from . import config, testcases
+from __main__ import options
+
+import os, re, sys
+
+try:
+	from collections import deque
+except ImportError:
+	deque = list
+
+try:
+	import signal
+except ImportError:
+	signalnames = ()
+else:
+	# Construct a cache of all signal names available on the current
+	# platform. Prefer names from the UNIX standards over other versions.
+	unixnames = frozenset(('HUP', 'INT', 'QUIT', 'ILL', 'ABRT', 'FPE', 'KILL', 'SEGV', 'PIPE', 'ALRM', 'TERM', 'USR1', 'USR2', 'CHLD', 'CONT', 'STOP', 'TSTP', 'TTIN', 'TTOU', 'BUS', 'POLL', 'PROF', 'SYS', 'TRAP', 'URG', 'VTALRM', 'XCPU', 'XFSZ'))
+	signalnames = {}
+	for name in dir(signal):
+		if re.match('SIG[A-Z]+$', name):
+			value = signal.__dict__[name]
+			if isinstance(value, int) and (value not in signalnames or name[3:] in unixnames):
+				signalnames[value] = name
+	del unixnames
+
+__all__ = 'Problem', 'TestContext', 'test_context_end', 'TestGroup'
+
+
+def strerror(e):
+	s = getattr(e, 'strerror', None)
+	if not s: s = str(e)
+	return ' (%s%s)' % (s[0].lower(), s[1:]) if s else ''
+
+
+class Cache(object):
+	def __init__(self, mydict):
+		self.__dict__ = mydict
+
+
+class TestContext(object):
+	__slots__ = ()
+
+test_context_end = object()
+
+class TestGroup(TestContext):
+	__slots__ = 'points', 'case', 'log', 'correct', 'allcorrect', 'real', 'max', 'ntotal', 'nvalued', 'ncorrect', 'ncorrectvalued'
+	
+	def __init__(self, points=None):
+		self.points = points
+		self.real = self.max = self.ntotal = self.nvalued = self.ncorrect = self.ncorrectvalued = 0
+		self.allcorrect = True
+		self.log = []
+	
+	def case_start(self, case):
+		self.case = case
+		self.correct = False
+		self.ntotal += 1
+		if case.points:
+			self.nvalued += 1
+	
+	def case_correct(self):
+		self.correct = True
+		self.ncorrect += 1
+		if self.case.points:
+			self.ncorrectvalued += 1
+	
+	def case_end(self):
+		self.log.append((self.case, self.correct))
+		del self.case
+		if not self.correct:
+			self.allcorrect = False
+	
+	def score(self, real, max):
+		self.real += real
+		self.max += max
+	
+	def end(self):
+		if not self.allcorrect:
+			self.real = 0
+		if self.points is not None and self.points != self.max:
+			max, weighted = self.points, self.real * self.points / self.max if self.max else 0
+			before_weighting = ' (%g/%g before weighting)' % (self.real, self.max)
+		else:
+			max, weighted = self.max, self.real
+			before_weighting = ''
+		say('Group total: %d/%d tests, %g/%g points%s' % (self.ncorrect, self.ntotal, weighted, max, before_weighting))
+		# No real need to flush stdout, as it will anyway be flushed in a moment,
+		# when either the problem total or the next test case's ID is printed
+		return weighted, max, self.log
+
+class DummyTestGroup(TestGroup):
+	__slots__ = ()
+	def end(self):
+		say('Sample total: %d/%d tests' % (self.ncorrect, self.ntotal))
+		return 0, 0, self.log
+
+
+class Problem(object):
+	__slots__ = 'name', 'config', 'cache', 'testcases'
+	
+	def __init__(prob, name):
+		if not isinstance(name, basestring):
+			# This shouldn't happen, of course
+			raise TypeError('Problem() argument 1 must be string, not ' + type(name).__name__)
+		prob.name = name
+		prob.config = config.load_problem(name)
+		prob.cache = Cache({'padoutput': 0})
+		prob.testcases = load_problem(prob)
+	
+	# TODO
+	def build(prob):
+		raise NotImplementedError
+	
+	def test(prob):
+		case = None
+		try:
+			contexts = deque((TestGroup(),))
+			for case in prob.testcases:
+				if case is test_context_end:
+					real, max, log = contexts.pop().end()
+					for case, correct in log:
+						contexts[-1].case_start(case)
+						if correct:
+							contexts[-1].case_correct()
+						contexts[-1].case_end()
+					contexts[-1].score(real, max)
+					continue
+				elif isinstance(case, TestContext):
+					contexts.append(case)
+					continue
+				contexts[-1].case_start(case)
+				granted = 0
+				id = str(case.id)
+				if case.isdummy:
+					id = 'sample ' + id
+				say('%*s: ' % (prob.cache.padoutput, id), end='')
+				sys.stdout.flush()
+				try:
+					if prob.config.kind != 'outonly':
+						granted = case(lambda: (say('%7.3f%s s, ' % (case.time_stopped - case.time_started, case.time_limit_string), end=''), sys.stdout.flush()))
+					else:
+						granted = case(lambda: None)
+				except TestCaseSkipped:
+					verdict = 'skipped due to skimming mode'
+				except CanceledByUser:
+					verdict = 'canceled by the user'
+				except WallTimeLimitExceeded:
+					verdict = 'wall-clock time limit exceeded'
+				except CPUTimeLimitExceeded:
+					verdict = 'CPU time limit exceeded'
+				except MemoryLimitExceeded:
+					verdict = 'memory limit exceeded'
+				except WrongAnswer:
+					e = sys.exc_info()[1]
+					if e.comment:
+						verdict = 'wrong answer (%s)' % e.comment
+					else:
+						verdict = 'wrong answer'
+				except NonZeroExitCode:
+					e = sys.exc_info()[1]
+					if e.exitcode < 0:
+						if sys.platform == 'win32':
+							verdict = 'terminated with error 0x%X' % (e.exitcode + 0x100000000)
+						elif -e.exitcode in signalnames:
+							verdict = 'terminated by signal %d (%s)' % (-e.exitcode, signalnames[-e.exitcode])
+						else:
+							verdict = 'terminated by signal %d' % -e.exitcode
+					else:
+						verdict = 'non-zero return code %d' % e.exitcode
+				except CannotStartTestee:
+					verdict = 'cannot launch the program to test%s' % strerror(sys.exc_info()[1].upstream)
+				except CannotStartValidator:
+					verdict = 'cannot launch the validator%s' % strerror(sys.exc_info()[1].upstream)
+				except CannotReadOutputFile:
+					verdict = 'cannot read the output file%s' % strerror(sys.exc_info()[1].upstream)
+				except CannotReadInputFile:
+					verdict = 'cannot read the input file%s' % strerror(sys.exc_info()[1].upstream)
+				except CannotReadAnswerFile:
+					verdict = 'cannot read the reference output file%s' % strerror(sys.exc_info()[1].upstream)
+				except ExceptionWrapper:
+					verdict = 'unspecified reason [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1].upstream)
+				except TestCaseNotPassed:
+					verdict = 'unspecified reason [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1])
+				#except Exception:
+				#	verdict = 'unknown error [this may be a bug in test.py]%s' % strerror(sys.exc_info()[1])
+				else:
+					try:
+						granted, comment = granted
+					except TypeError:
+						comment = ''
+					else:
+						if comment:
+							comment = ' (%s)' % comment
+					if granted >= 1:
+						contexts[-1].case_correct()
+						prob.testcases.send(True)
+						verdict = 'OK' + comment
+					elif not granted:
+						verdict = 'wrong answer' + comment
+					else:
+						verdict = 'partly correct' + comment
+					granted *= case.points
+				say('%g/%g, %s' % (granted, case.points, verdict))
+				contexts[-1].case_end()
+				contexts[-1].score(granted, case.points)
+			weighted = contexts[0].real * prob.config.taskweight / contexts[0].max if contexts[0].max else 0
+			before_weighting = valued = ''
+			if prob.config.taskweight != contexts[0].max:
+				before_weighting = ' (%g/%g before weighting)' % (contexts[0].real, contexts[0].max)
+			if contexts[0].nvalued != contexts[0].ntotal:
+				valued = ' (%d/%d valued)' % (contexts[0].ncorrectvalued, contexts[0].nvalued)
+			say('Problem total: %d/%d tests%s, %g/%g points%s' % (contexts[0].ncorrect, contexts[0].ntotal, valued, weighted, prob.config.taskweight, before_weighting))
+			sys.stdout.flush()
+			return weighted, prob.config.taskweight
+		finally:
+			if options.erase and case and case.has_iofiles:
+				for var in 'in', 'out':
+					name = getattr(prob.config, var + 'name')
+					if name:
+						try:
+							os.remove(name)
+						except Exception:
+							pass
+				if case.has_ansfile:
+					if prob.config.ansname:
+						try:
+							os.remove(prob.config.ansname)
+						except Exception:
+							pass
+
+
+def load_problem(prob, _types={'batch'  : testcases.BatchTestCase,
+                               'outonly': testcases.OutputOnlyTestCase}):
+	# We will need to iterate over these configuration variables twice
+	try:
+		len(prob.config.dummies)
+	except Exception:
+		prob.config.dummies = tuple(prob.config.dummies)
+	try:
+		len(prob.config.tests)
+	except Exception:
+		prob.config.tests = tuple(prob.config.tests)
+	
+	if options.legacy:
+		prob.config.usegroups = False
+		newtests = []
+		for i, name in enumerate(prob.config.tests):
+			# Same here; we'll need to iterate over them twice
+			try:
+				l = len(name)
+			except Exception:
+				try:
+					name = tuple(name)
+				except TypeError:
+					name = (name,)
+				l = len(name)
+			if l > 1:
+				prob.config.usegroups = True
+			newtests.append(name)
+		if prob.config.usegroups:
+			prob.config.tests = newtests
+		del newtests
+	
+	# Even if they have duplicate test identifiers, we must honour sequence pointmaps
+	if isinstance(prob.config.pointmap, dict):
+		def getpoints(i, j, k=None):
+			try:
+				return prob.config.pointmap[i]
+			except KeyError:
+				try:
+					return prob.config.pointmap[None]
+				except KeyError:
+					return prob.config.maxexitcode or 1
+	elif prob.config.usegroups:
+		def getpoints(i, j, k):
+			try:
+				return prob.config.pointmap[k][j]
+			except LookupError:
+				return prob.config.maxexitcode or 1
+	else:
+		def getpoints(i, j):
+			try:
+				return prob.config.pointmap[j]
+			except LookupError:
+				return prob.config.maxexitcode or 1
+	
+	# First get prob.cache.padoutput right,
+	# then yield the actual test cases
+	for i in prob.config.dummies:
+		s = 'sample ' + str(i).zfill(prob.config.paddummies)
+		prob.cache.padoutput = max(prob.cache.padoutput, len(s))
+	if prob.config.usegroups:
+		if not isinstance(prob.config.groupweight, dict):
+			prob.config.groupweight = dict(enumerate(prob.config.groupweight))
+		for group in prob.config.tests:
+			for i in group:
+				s = str(i).zfill(prob.config.padtests)
+				prob.cache.padoutput = max(prob.cache.padoutput, len(s))
+		if prob.config.dummies:
+			yield DummyTestGroup()
+			for i in prob.config.dummies:
+				s = str(i).zfill(prob.config.paddummies)
+				if (yield _types[prob.config.kind](prob, s, True, 0)):
+					yield
+			yield test_context_end
+		for k, group in enumerate(prob.config.tests):
+			if not group:
+				continue
+			yield 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)
+				if not (yield case_type(prob, s, False, getpoints(i, j, k))):
+					if options.skim:
+						case_type = testcases.SkippedTestCase
+				else:
+					yield
+			yield test_context_end
+	else:
+		for i in prob.config.tests:
+			s = str(i).zfill(prob.config.padtests)
+			prob.cache.padoutput = max(prob.cache.padoutput, len(s))
+		for i in prob.config.dummies:
+			s = str(i).zfill(prob.config.paddummies)
+			if (yield _types[prob.config.kind](prob, s, True, 0)):
+				yield
+		for j, i in enumerate(prob.config.tests):
+			s = str(i).zfill(prob.config.padtests)
+			if (yield _types[prob.config.kind](prob, s, False, getpoints(i, j))):
+				yield
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/publish.sh	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,11 @@
+#! /bin/sh
+
+VERSION=`hg identify | awk '{ print $1 }'`
+if [ -z "$VERSION" ]
+then
+	echo The current Mercurial changeset could not be determined. >&2
+	exit 1
+fi
+
+sed 's/$$REV$\$/hg '"$VERSION/" upreckon-vcs >upreckon
+chmod +x upreckon
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/testcases.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,295 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+# TODO: copy the ansfile if not options.erase even if no validator is used
+
+from __future__ import division, with_statement
+
+from .compat import *
+from .exceptions import *
+from . import files, config
+from __main__ import options
+
+import re, sys, tempfile
+from subprocess import Popen, PIPE, STDOUT
+
+import os
+devnull = open(os.path.devnull, 'w+')
+
+class DummySignalIgnorer(object):
+	def __enter__(self): pass
+	def __exit__(self, exc_type, exc_value, traceback): pass
+signal_ignorer = DummySignalIgnorer()
+
+try:
+	from .win32 import *
+except Exception:
+	from .unix import *
+
+__all__ = ('TestCase', 'SkippedTestCase', 'ValidatedTestCase', 'BatchTestCase',
+           'OutputOnlyTestCase')
+
+
+
+# Helper context managers
+
+class CopyDeleting(object):
+	__slots__ = 'case', 'file', 'name'
+	
+	def __init__(self, case, file, name):
+		self.case = case
+		self.file = file
+		self.name = name
+	
+	def __enter__(self):
+		if self.name:
+			try:
+				self.file.copy(self.name)
+			except:
+				try:
+					self.__exit__(None, None, None)
+				except:
+					pass
+				raise
+	
+	def __exit__(self, exc_type, exc_val, exc_tb):
+		if self.name:
+			self.case.files_to_delete.append(self.name)
+
+
+class Copying(object):
+	__slots__ = 'file', 'name'
+	
+	def __init__(self, file, name):
+		self.file = file
+		self.name = name
+	
+	def __enter__(self):
+		if self.name:
+			self.file.copy(self.name)
+	
+	def __exit__(self, exc_type, exc_val, exc_tb):
+		pass
+
+
+
+# Test case types
+
+class TestCase(object):
+	__slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points',
+	             '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')
+	has_ansfile = has_iofiles = False
+	needs_realinname = True
+	
+	if ABCMeta:
+		__metaclass__ = ABCMeta
+	
+	def __init__(case, prob, id, isdummy, points):
+		case.problem = prob
+		case.id = id
+		case.isdummy = isdummy
+		case.points = points
+		case.maxcputime = case.problem.config.maxcputime
+		case.maxwalltime = case.problem.config.maxwalltime
+		case.maxmemory = case.problem.config.maxmemory
+		if case.maxcputime:
+			case.cpu_time_limit_string = '/%.3f' % case.maxcputime
+		else:
+			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:
+			if case.needs_realinname:
+				case.realinname = case.problem.config.testcaseinname
+			case.realoutname = case.problem.config.testcaseoutname
+		else:
+			if case.needs_realinname:
+				case.realinname = case.problem.config.dummyinname
+			case.realoutname = case.problem.config.dummyoutname
+	
+	@abstractmethod
+	def test(case):
+		raise NotImplementedError
+	
+	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 getattr(case, 'time_started', None) is None:
+				case.time_started = case.time_stopped = now
+			elif getattr(case, 'time_stopped', None) is None:
+				case.time_stopped = now
+			if not case.has_called_back:
+				callback()
+			case.cleanup()
+	
+	def cleanup(case):
+		# Note that native extensions clean up on their own
+		# and never let this condition be satisfied
+		if getattr(case, 'process', None) and case.process.returncode is None:
+			kill(case.process)
+		for name in case.files_to_delete:
+			try:
+				os.remove(name)
+			except OSError:
+				# It can't be helped
+				pass
+	
+	def open_infile(case):
+		try:
+			case.infile = files.File('/'.join((case.problem.name, case.realinname.replace('$', case.id))))
+		except IOError:
+			e = sys.exc_info()[1]
+			raise CannotReadInputFile(e)
+	
+	def open_outfile(case):
+		try:
+			case.outfile = files.File('/'.join((case.problem.name, case.realoutname.replace('$', case.id))))
+		except IOError:
+			e = sys.exc_info()[1]
+			raise CannotReadAnswerFile(e)
+
+
+class SkippedTestCase(TestCase):
+	__slots__ = ()
+	
+	def test(case, callback):
+		raise TestCaseSkipped
+
+
+class ValidatedTestCase(TestCase):
+	__slots__ = 'validator'
+	
+	def __init__(case, *args):
+		TestCase.__init__(case, *args)
+		if not case.problem.config.tester:
+			case.validator = None
+		else:
+			case.validator = case.problem.config.tester
+	
+	def validate(case, output):
+		if not case.validator:
+			# Compare the output with the reference output
+			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:
+						raise WrongAnswer
+			return 1
+		elif callable(case.validator):
+			return case.validator(output)
+		else:                 
+			# Call the validator program
+			output.close()
+			if case.problem.config.ansname:
+				case.open_outfile()
+				case.outfile.copy(case.problem.config.ansname)
+			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])
+			with signal_ignorer:
+				comment = case.process.communicate()[0].strip()
+			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:
+				if case.process.returncode:
+					raise WrongAnswer(comment)
+				else:
+					return 1, comment
+			else:
+				return case.process.returncode / case.problem.config.maxexitcode, comment
+
+
+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()
+		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
+				# FIXME: 2.5 lacks the delete parameter
+				with tempfile.NamedTemporaryFile(delete=False) as f:
+					inputdatafname = f.name
+				contextmgr = CopyDeleting(case, case.infile, inputdatafname)
+			else:
+				inputdatafname = case.problem.config.inname
+				contextmgr = Copying(case.infile, inputdatafname)
+			with contextmgr:
+				with open(inputdatafname) as infile:
+					with tempfile.TemporaryFile('w+') if options.erase and (not case.validator or callable(case.validator)) else open(case.problem.config.outname, 'w+') as outfile:
+						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:
+							raise NonZeroExitCode(case.process.returncode)
+						case.has_called_back = True
+						callback()
+						outfile.seek(0)
+						return case.validate(outfile)
+		else:
+			case.infile.copy(case.problem.config.inname)
+			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)
+			case.has_called_back = True
+			callback()
+			try:
+				output = open(case.problem.config.outname, 'rU')
+			except IOError:
+				raise CannotReadOutputFile(sys.exc_info()[1])
+			with output as output:
+				return case.validate(output)
+
+
+# This is the only test case type not executing any programs to be tested
+class OutputOnlyTestCase(ValidatedTestCase):
+	__slots__ = ()
+	needs_realinname = False
+	
+	def cleanup(case):
+		pass
+	
+	def test(case, callback):
+		case.time_stopped = case.time_started = 0
+		case.has_called_back = True
+		callback()
+		try:
+			output = open(case.problem.config.outname.replace('$', case.id), 'rU')
+		except IOError:
+			raise CannotReadOutputFile(sys.exc_info()[1])
+		with output as output:
+			return case.validate(output)
+
+
+class BestOutputTestCase(ValidatedTestCase):
+	__slots__ = ()
+
+
+# This is the only test case type executing two programs simultaneously
+class ReactiveTestCase(TestCase):
+	__slots__ = ()
+	# The basic idea is to launch the program to be tested and the grader
+	# and to pipe their standard I/O from and to each other,
+	# and then to capture the grader's exit code and use it
+	# like the exit code of an output validator is used.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/unix.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,310 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+
+from .compat import *
+from .exceptions import *
+
+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 signal import SIGTERM, SIGKILL
+except ImportError:
+	SIGTERM = 15
+	SIGKILL = 9
+
+__all__ = 'call', 'kill', 'pause', 'clock'
+
+
+try:
+	from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd
+	from select import select, error as SelectError
+	from errno import EAGAIN, EINTR
+	from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL
+	from os import O_NONBLOCK
+	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.maxwalltime:
+			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.maxwalltime
+			while True:
+				exitcode, now = case.process.poll(), clock()
+				if exitcode is not None:
+					case.time_stopped = now
+					break
+				elif now >= time_end:
+					raise WallTimeLimitExceeded
+				else:
+					time.sleep(.001)
+else:
+	try:
+		from fcntl import FD_CLOEXEC
+	except ImportError:
+		FD_CLOEXEC = 1
+	
+	try:
+		from signal import siginterrupt
+	except ImportError:
+		# Sucks.
+		siginterrupt = lambda signalnum, flag: None
+	
+	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 as RLIMIT_AS
+	except ImportError:
+		setrlimit = None
+	
+	# 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)
+	def bury_child(signum, frame):
+		try:
+			bury_child.case.time_stopped = clock()
+		except Exception:
+			pass
+	signal(SIGCHLD, bury_child)
+	set_wakeup_fd(sigchld_pipe_write)
+	class SignalIgnorer(object):
+		def __enter__(self):
+			signal(SIGCHLD, SIG_DFL)
+		def __exit__(self, exc_type, exc_value, traceback):
+			signal(SIGCHLD, bury_child)
+	signal_ignorer = SignalIgnorer()
+	__all__ += 'signal_ignorer',
+	
+	# 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
+		# So how the hell do I actually make use of pass_fds?
+		# On 3.1-, calling Popen with pass_fds prints an exception
+		# from Popen.__del__ to stderr. On 3.2, Popen without close_fds
+		# or pass_fds creates a child and fails but that of course
+		# generates a SIGCHLD, which causes problems, and I have
+		# no process ID to wait upon to negate the changes made
+		# by the SIGCHLD handler.
+		kwargs['close_fds'] = False
+		old_rusage = getrusage(RUSAGE_CHILDREN)
+		last_rusage = None
+		while True:
+			try:
+				os.read(sigchld_pipe_read, 512)
+			except OSError:
+				if sys.exc_info()[1].errno == EAGAIN:
+					break
+				else:
+					raise
+		siginterrupt(SIGCHLD, False)
+		try:
+			case.process = Popen(*args, **kwargs)
+		except OSError:
+			os.close(read)
+			raise CannotStartTestee(sys.exc_info()[1])
+		finally:
+			siginterrupt(SIGCHLD, True)
+			os.close(write)
+		try:
+			if not catch_escape:
+				if case.maxwalltime:
+					try:
+						select((sigchld_pipe_read,), (), (), case.maxwalltime)
+					except SelectError:
+						if sys.exc_info()[1].args[0] != EINTR:
+							raise
+					# subprocess in Python 2.6- is not guarded against EINTR
+					try:
+						if case.process.poll() is None:
+							raise WallTimeLimitExceeded
+					except OSError:
+						if sys.exc_info()[1].errno != EINTR:
+							raise
+						else:
+							case.process.poll()
+				else:
+					wait(case.process)
+			else:
+				if not case.maxwalltime:
+					try:
+						while case.process.poll() is None:
+							s = select((sys.stdin, sigchld_pipe_read), (), ())
+							if (s[0] == [sys.stdin] and
+							    sys.stdin.read(1) == '\33'):
+								raise CanceledByUser
+					except (SelectError, IOError, OSError):
+						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:
+								s = select((sys.stdin, sigchld_pipe_read),
+								           (), (), remaining)
+								if (s[0] == [sys.stdin] and
+								    sys.stdin.read(1) == '\33'):
+									raise CanceledByUser
+							else:
+								raise WallTimeLimitExceeded
+					except (SelectError, IOError, OSError):
+						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 (case.maxwalltime and
+		    case.time_stopped - case.time_started > case.maxwalltime):
+			raise WallTimeLimitExceeded
+		if new_rusage:
+			time_started = old_rusage.ru_utime + old_rusage.ru_stime + cpustart
+			time_stopped = new_rusage.ru_utime + new_rusage.ru_stime
+			# Yes, this actually happens
+			if time_started > time_stopped:
+				time_started = time_stopped
+			if case.maxcputime or not case.maxwalltime:
+				case.time_started = time_started
+				case.time_stopped = time_stopped
+				case.time_limit_string = case.cpu_time_limit_string
+				if (case.maxcputime and
+				    time_stopped - time_started > case.maxcputime):
+					raise 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 MemoryLimitExceeded
+			elif (new_rusage and
+			      new_rusage.ru_maxrss > old_rusage.ru_maxrss and
+			      new_rusage.ru_maxrss > maxrss):
+				raise 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)
+	wait(process)
+
+
+# subprocess in Python 2.6- is not guarded against EINTR
+try:
+	from errno import EINTR
+except ImportError:
+	wait = Popen.wait
+else:
+	def wait(process):
+		while True:
+			try:
+				return process.wait()
+			except OSError:
+				if sys.exc_info()[1].errno != EINTR:
+					raise
+
+
+try:
+	from ._unix import *
+except ImportError:
+	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 = lambda: sys.stdin.read(1)
+			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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/upreckon-vcs	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,122 @@
+#! /usr/bin/env python
+# Copyright (c) 2009-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+import optparse
+
+from upreckon import compat
+from upreckon.compat import *
+
+version = '2.01.0 ($$REV$$)'
+parser = optparse.OptionParser(version='Upreckon '+version, epilog='Python 2.5 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('-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; delete the stored input/output files if the solution uses standard I/O and the -c/--cleanup option is specified')
+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('-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()
+parser.destroy()
+del parser
+
+from upreckon import config
+import itertools, os, subprocess, sys, time
+
+if options.legacy:
+	compat.pseudobuiltins += 'xrange',
+
+if options.list_problems:
+	options.pause = False
+
+from upreckon import testcases
+
+try:
+	from upreckon.testcases import pause
+except ImportError:
+	pause = None
+
+try:
+	globalconf = config.load_global()
+	
+	# Do this check here so that if we have to warn them, we do it as early as possible
+	if options.pause and not pause and not hasattr(globalconf, 'pause'):
+		if os.name == 'posix':
+			globalconf.pause = 'read -s -n 1'
+			say('Warning: configuration variable pause is not defined; it was devised automatically but the choice might be incorrect, so Upreckon might exit immediately after the testing is completed.', file=sys.stderr)
+			sys.stderr.flush()
+		elif os.name == 'nt':
+			globalconf.pause = 'pause'
+		else:
+			sys.exit('Error: configuration variable pause is not defined and cannot be devised automatically.')
+	
+	from upreckon.problem import *
+	
+	# Support single-problem configurations
+	if globalconf.problems is None:
+		shouldprintnames = False
+		globalconf.multiproblem = False
+		globalconf.problems = os.path.curdir,
+	else:
+		globalconf.multiproblem = True
+		shouldprintnames = True
+	
+	if options.list_problems:
+		for taskname in globalconf.problems:
+			say(taskname)
+		sys.exit()
+	
+	ntasks = 0
+	nfulltasks = 0
+	maxscore = 0
+	realscore = 0
+	
+	for taskname in (globalconf.problems if not options.problems else options.problems):
+		problem = Problem(taskname)
+		
+		if ntasks and not options.copyonly: say()
+		if shouldprintnames: say(taskname)
+		
+		if options.copyonly:
+			problem.copytestdata()
+		else:
+			real, max = problem.test()
+		
+		ntasks += 1
+		nfulltasks += real == max
+		realscore += real
+		maxscore += max
+	
+	if options.copyonly:
+		sys.exit()
+	
+	if ntasks != 1:
+		say()
+		say('Grand total: %g/%g weighted points; %d/%d problems solved fully' % (realscore, maxscore, nfulltasks, ntasks))
+except KeyboardInterrupt:
+	say('Exiting due to a keyboard interrupt.', end='', file=sys.stderr)
+	sys.stderr.flush()
+	try:
+		import signal
+		signal.signal(signal.SIGINT, signal.SIG_DFL)
+		os.kill(os.getpid(), signal.SIGINT)
+	except Exception:
+		pass
+	# Do this even if we got no exceptions, just in case
+	say(file=sys.stderr)
+	sys.exit(1)
+
+if options.pause:
+	say('Press any key to exit...')
+	sys.stdout.flush()
+	
+	if pause:
+		pause()
+	elif callable(globalconf.pause):
+		globalconf.pause()
+	else:
+		with open(os.devnull, 'w') as devnull:
+			subprocess.call(globalconf.pause, shell=True, stdout=devnull, stderr=subprocess.STDOUT)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/upreckon.cmd	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,1 @@
+@ start /b /wait python "%~dpn0" %*
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upreckon/win32.py	Sat May 28 14:24:25 2011 +0100
@@ -0,0 +1,557 @@
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+
+from .compat import *
+from .exceptions import *
+
+from ctypes import *
+from ctypes.wintypes import *
+from msvcrt import getch as pause
+from time import clock
+import os, subprocess, sys, time
+
+try:
+	from _winreg import *
+except ImportError:
+	from winreg import *
+
+# Defaults that may be overwritten by values from _subprocess
+INFINITE = -1
+STD_INPUT_HANDLE = -10
+WAIT_OBJECT_0 = 0
+
+try:
+	from _subprocess import *
+except ImportError:
+	pass
+
+try:
+	from numbers import Integral
+except ImportError:
+	Integral = int, long
+
+try:
+	from collections import namedtuple
+except ImportError:
+	from operator import itemgetter
+	class ProcessTimes(tuple):
+		__slots__ = ()
+		def __new__(cls, creation, exit, kernel, user):
+			return tuple.__new__(cls, (creation, exit, kernel, user))
+		__getnewargs__ = lambda self: tuple(self)
+		creation, exit, kernel, user = map(property, map(itemgetter, range(4)))
+else:
+	ProcessTimes = namedtuple('ProcessTimes', 'creation exit kernel user')
+
+__all__ = 'call', 'kill', 'pause', 'clock'
+
+
+from functools import wraps
+pathext = [''] + os.environ['PATHEXT'].split(';')
+@wraps(subprocess.Popen)
+def Popen(cmdline, *args, **kwargs):
+	try:
+		return subprocess.Popen(cmdline, *args, **kwargs)
+	except WindowsError:
+		for ext in pathext:
+			path = cmdline[0] + ext
+			newcmdline = type(cmdline)((path,)) + cmdline[1:]
+			try:
+				return subprocess.Popen(newcmdline, *args, **kwargs)
+			except WindowsError:
+				pass
+			for branch in HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE:
+				try:
+					path = (R'SOFTWARE\Microsoft\Windows\CurrentVersion'
+					        R'\App Paths\%s%s' % (cmdline[0], ext))
+					path = QueryValue(branch, path)
+					break
+				except WindowsError:
+					pass
+			else:
+				continue
+			if path[0] == '"' == path[-1]:
+				path = path[1:-1]
+			newcmdline = type(cmdline)((path,)) + cmdline[1:]
+			try:
+				return subprocess.Popen(newcmdline, *args, **kwargs)
+			except WindowsError:
+				pass
+		# I'd like to transparently re-raise the exception generated
+		# on the very first try, but syntax differences preclude me from
+		# doing so in Python 2 and it can't be done at all in Python 3
+		raise
+
+
+# Automatically convert _subprocess handle objects into low-level
+# HANDLEs and replicate their functionality for our own use
+try:
+	_subprocess_handle = type(GetCurrentProcess())
+except NameError:
+	_subprocess_handle = Integral
+class Handle(object):
+	@staticmethod
+	def from_param(handle):
+		if isinstance(handle, (_subprocess_handle, Integral)):
+			return HANDLE(int(handle))
+		elif isinstance(handle, Handle):
+			return HANDLE(handle.handle)
+		elif isinstance(handle, HANDLE):
+			return handle
+		else:
+			raise TypeError('cannot convert %s to a handle' %
+			                type(handle).__name__)
+	
+	__slots__ = 'handle'
+	
+	def __init__(self, handle):
+		if isinstance(handle, Integral):
+			self.handle = handle
+		elif isinstance(handle, HANDLE):
+			self.handle = handle.value
+		elif isinstance(handle, Handle):
+			self.handle = handle.handle
+		elif isinstance(handle, _subprocess_handle):
+			handle = HANDLE(int(handle))
+			flags = DWORD()
+			try:
+				if windll.kernel32.GetHandleInformation(handle, byref(flags)):
+					flags = flags.value
+				else:
+					flags = 0
+			except AttributeError:
+				# Available on NT 3.51 and up, NT line only
+				flags = 0
+			proc = HANDLE(int(GetCurrentProcess()))
+			handle = DuplicateHandle(proc, handle, proc, 0, flags & 1, 2)
+			self.handle = handle.Detach()
+		else:
+			raise TypeError("Handle() argument must be a handle, not '%s'" %
+			                type(handle).__name__)
+	
+	def __int__(self):
+		return int(self.handle)
+	
+	def Detach(self):
+		handle = self.handle
+		self.handle = None
+		return handle
+	
+	# This is also __del__, so only locals are accessed
+	def Close(self, _CloseHandle=windll.kernel32.CloseHandle, _HANDLE=HANDLE):
+		if getattr(self, 'handle', None):
+			_CloseHandle(_HANDLE(self.handle))
+			self.handle = None
+	__del__ = Close
+
+CHAR = c_char
+INVALID_HANDLE_VALUE = HANDLE(-1).value
+LPDWORD = POINTER(DWORD)
+LPFILETIME = POINTER(FILETIME)
+SIZE_T = ULONG_PTR = WPARAM
+ULONGLONG = c_ulonglong
+
+try:
+	unicode
+except NameError:
+	LPCTSTR = LPCWSTR
+	UNISUFFIX = 'W'
+else:
+	LPCTSTR = LPCSTR
+	UNISUFFIX = 'A'
+
+
+prototype = WINFUNCTYPE(BOOL, Handle,
+                        LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME)
+flags = ((1, 'process'),
+         (2, 'creation'), (2, 'exit'), (2, 'kernel'), (2, 'user'))
+try:
+	GetProcessTimes = prototype(('GetProcessTimes', windll.kernel32), flags)
+except AttributeError:
+	# Available on NT 3.5 and up, NT line only
+	GetProcessTimes = None
+else:
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+		times = ((t.dwHighDateTime << 32 | t.dwLowDateTime) / 10000000
+		         for t in args[1:])
+		return ProcessTimes(*times)
+	GetProcessTimes.errcheck = errcheck
+
+
+class PROCESS_MEMORY_COUNTERS(Structure):
+	_fields_ = (('cb', DWORD),
+	            ('PageFaultCount', DWORD),
+	            ('PeakWorkingSetSize', SIZE_T),
+	            ('WorkingSetSize', SIZE_T),
+	            ('QuotaPeakPagedPoolUsage', SIZE_T),
+	            ('QuotaPagedPoolUsage', SIZE_T),
+	            ('QuotaPeakNonPagedPoolUsage', SIZE_T),
+	            ('QuotaNonPagedPoolUsage', SIZE_T),
+	            ('PagefileUsage', SIZE_T),
+	            ('PeakPagefileUsage', SIZE_T))
+
+prototype = WINFUNCTYPE(BOOL, Handle, POINTER(PROCESS_MEMORY_COUNTERS), DWORD)
+flags = ((1, 'process'), (2, 'counters'),
+         (5, 'cb', sizeof(PROCESS_MEMORY_COUNTERS)))
+try:
+	GetProcessMemoryInfo = prototype(('GetProcessMemoryInfo', windll.psapi),
+	                                 flags)
+except AttributeError:
+	# Available on NT 4.0 and up, NT line only
+	GetProcessMemoryInfo = None
+else:
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+		return args
+	GetProcessMemoryInfo.errcheck = errcheck
+
+
+class _uChar_union(Union):
+	_fields_ = (('UnicodeChar', WCHAR),
+	            ('AsciiChar', CHAR))
+
+class KEY_EVENT_RECORD(Structure):
+	_fields_ = (('bKeyDown', BOOL),
+	            ('wRepeatCount', WORD),
+	            ('wVirtualKeyCode', WORD),
+	            ('wVirtualScanCode', WORD),
+	            ('uChar', _uChar_union),
+	            ('dwControlKeyState', DWORD))
+
+RIGHT_ALT_PRESSED  = 0x001
+LEFT_ALT_PRESSED   = 0x002
+RIGHT_CTRL_PRESSED = 0x004
+LEFT_CTRL_PRESSED  = 0x008
+SHIFT_PRESSED      = 0x010
+NUMLOCK_ON         = 0x020
+SCROLLLOCK_ON      = 0x040
+CAPSLOCK_ON        = 0x080
+ENHANCED_KEY       = 0x100
+
+class _Event_union(Union):
+	_fields_ = ('KeyEvent', KEY_EVENT_RECORD),
+
+class INPUT_RECORD(Structure):
+	_fields_ = (('EventType', WORD),
+	            ('Event', _Event_union))
+
+KEY_EVENT                = 0x01
+MOUSE_EVENT              = 0x02
+WINDOW_BUFFER_SIZE_EVENT = 0x04
+MENU_EVENT               = 0x08
+FOCUS_EVENT              = 0x10
+
+prototype = WINFUNCTYPE(BOOL, Handle, POINTER(INPUT_RECORD), DWORD, LPDWORD)
+flags = (1, 'input'), (2, 'buffer'), (5, 'length', 1), (2, 'number_read')
+ReadConsoleInput = prototype(('ReadConsoleInputA', windll.kernel32), flags)
+def errcheck(result, func, args):
+	if not result: raise WinError()
+	return args[1] if args[3] else None
+ReadConsoleInput.errcheck = errcheck
+
+
+prototype = WINFUNCTYPE(BOOL, Handle)
+flags = (1, 'input'),
+FlushConsoleInputBuffer = prototype(('FlushConsoleInputBuffer',
+                                     windll.kernel32), flags)
+def errcheck(result, func, args):
+	if not result: raise WinError()
+FlushConsoleInputBuffer.errcheck = errcheck
+
+
+prototype = WINFUNCTYPE(BOOL, Handle, DWORD)
+flags = (1, 'console'), (1, 'mode')
+SetConsoleMode = prototype(('SetConsoleMode', windll.kernel32), flags)
+def errcheck(result, func, args):
+	if not result: raise WinError()
+SetConsoleMode.errcheck = errcheck
+
+ENABLE_PROCESSED_INPUT = 0x001
+ENABLE_LINE_INPUT      = 0x002
+ENABLE_ECHO_INPUT      = 0x004
+ENABLE_WINDOW_INPUT    = 0x008
+ENABLE_MOUSE_INPUT     = 0x010
+ENABLE_INSERT_MODE     = 0x020
+ENABLE_QUICK_EDIT_MODE = 0x040
+ENABLE_EXTENDED_FLAGS  = 0x080
+
+ENABLE_PROCESSED_OUTPUT   = 1
+ENABLE_WRAP_AT_EOL_OUTPUT = 2
+
+
+prototype = WINFUNCTYPE(HANDLE, c_void_p, LPCTSTR)
+flags = (5, 'attributes'), (1, 'name')
+try:
+	CreateJobObject = prototype(('CreateJobObject'+UNISUFFIX, windll.kernel32),
+	                            flags)
+except AttributeError:
+	# Available on 2000 and up, NT line only
+	CreateJobObject = lambda name: None
+else:
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+		return Handle(result)
+	CreateJobObject.errcheck = errcheck
+
+
+prototype = WINFUNCTYPE(BOOL, Handle, Handle)
+flags = (1, 'job'), (1, 'handle')
+try:
+	AssignProcessToJobObject = prototype(('AssignProcessToJobObject',
+	                                      windll.kernel32), flags)
+except AttributeError:
+	# Available on 2000 and up, NT line only
+	AssignProcessToJobObject = lambda job, handle: None
+else:
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+	AssignProcessToJobObject.errcheck = errcheck
+
+
+class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure):
+	_fields_ = (('PerProcessUserTimeLimit', LARGE_INTEGER),
+	            ('PerJobUserTimeLimit', LARGE_INTEGER),
+	            ('LimitFlags', DWORD),
+	            ('MinimumWorkingSetSize', SIZE_T),
+	            ('MaximumWorkingSetSize', SIZE_T),
+	            ('ActiveProcessLimit', DWORD),
+	            ('Affinity', ULONG_PTR),
+	            ('PriorityClass', DWORD),
+	            ('SchedulingClass', DWORD))
+
+JOB_OBJECT_LIMIT_WORKINGSET                 = 0x0001
+JOB_OBJECT_LIMIT_PROCESS_TIME               = 0x0002
+JOB_OBJECT_LIMIT_JOB_TIME                   = 0x0004
+JOB_OBJECT_LIMIT_ACTIVE_PROCESS             = 0x0008
+JOB_OBJECT_LIMIT_AFFINITY                   = 0x0010
+JOB_OBJECT_LIMIT_PRIORITY_CLASS             = 0x0020
+JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME          = 0x0040
+JOB_OBJECT_LIMIT_SCHEDULING_CLASS           = 0x0080
+JOB_OBJECT_LIMIT_PROCESS_MEMORY             = 0x0100
+JOB_OBJECT_LIMIT_JOB_MEMORY                 = 0x0200
+JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x0400
+JOB_OBJECT_LIMIT_BREAKAWAY_OK               = 0x0800
+JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK        = 0x1000
+JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE          = 0x2000
+JOB_OBJECT_LIMIT_SUBSET_AFFINITY            = 0x4000
+
+class IO_COUNTERS(Structure):
+	_fields_ = (('ReadOperationCount', ULONGLONG),
+	            ('WriteOperationCount', ULONGLONG),
+	            ('OtherOperationCount', ULONGLONG),
+	            ('ReadTransferCount', ULONGLONG),
+	            ('WriteTransferCount', ULONGLONG),
+	            ('OtherTransferCount', ULONGLONG))
+
+class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure):
+	_fields_ = (('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION),
+	            ('IoInfo', IO_COUNTERS),
+	            ('ProcessMemoryLimit', SIZE_T),
+	            ('JobMemoryLimit', SIZE_T),
+	            ('PeakProcessMemoryUsed', SIZE_T),
+	            ('PeakJobMemoryUsed', SIZE_T))
+
+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)
+except AttributeError:
+	# Available on 2000 and up, NT line only
+	SetInformationJobObject = lambda job, infoclass, info: None
+else:
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+	_setjobinfo.errcheck = errcheck
+	def SetInformationJobObject(job, infoclass, info):
+		return _setjobinfo(job, infoclass, byref(info), sizeof(info))
+
+(
+	JobObjectBasicAccountingInformation,
+	JobObjectBasicLimitInformation,
+	JobObjectBasicProcessIdList,
+	JobObjectBasicUIRestrictions,
+	JobObjectSecurityLimitInformation,   
+	JobObjectEndOfJobTimeInformation,
+	JobObjectAssociateCompletionPortInformation,
+	JobObjectBasicAndIoAccountingInformation,
+	JobObjectExtendedLimitInformation,
+	JobObjectJobSetInformation,
+	MaxJobObjectInfoClass
+) = range(1, 12)
+
+
+prototype = WINFUNCTYPE(DWORD, DWORD, POINTER(HANDLE), BOOL, DWORD)
+flags = (1, 'count'), (1, 'handles'), (1, 'wait_all'), (1, 'milliseconds')
+_wait_multiple = prototype(('WaitForMultipleObjects', windll.kernel32), flags)
+def errcheck(result, func, args):
+	if result == WAIT_FAILED: raise WinError()
+	return args
+_wait_multiple.errcheck = errcheck
+def WaitForMultipleObjects(handles, wait_all, timeout):
+	n = len(handles)
+	handles = (Handle.from_param(handle) for handle in handles)
+	timeout = ceil(timeout * 1000)
+	return _wait_multiple(n, (HANDLE * n)(*handles), wait_all, timeout)
+
+# WAIT_OBJECT_0 defined at the top of the file
+WAIT_ABANDONED_0 = 0x00000080
+WAIT_TIMEOUT     = 0x00000102
+WAIT_FAILED      = 0xFFFFFFFF
+
+
+try:
+	_wait_single = WaitForSingleObject
+except NameError:
+	prototype = WINFUNCTYPE(DWORD, Handle, DWORD)
+	flags = (1, 'handle'), (1, 'milliseconds')
+	_wait_single = prototype(('WaitForSingleObject', windll.kernel32), flags)
+	def errcheck(result, func, args):
+		if result == WAIT_FAILED: raise WinError()
+		return args
+	_wait_single.errcheck = errcheck
+def WaitForSingleObject(handle, timeout):
+	return _wait_single(handle, ceil(timeout * 1000))
+
+
+try:
+	GetStdHandle
+except NameError:
+	prototype = WINFUNCTYPE(HANDLE, DWORD)
+	flags = (1, 'which'),
+	GetStdHandle = prototype(('GetStdHandle', windll.kernel32), flags)
+	def errcheck(result, func, args):
+		if result == INVALID_HANDLE_VALUE: raise WinError()
+		return args if result else None
+	GetStdHandle.errcheck = errcheck
+
+
+try:
+	TerminateProcess
+except NameError:
+	prototype = WINFUNCTYPE(BOOL, Handle, UINT)
+	flags = (1, 'process'), (1, 'exitcode')
+	TerminateProcess = prototype(('TerminateProcess', windll.kernel32), flags)
+	def errcheck(result, func, args):
+		if not result: raise WinError()
+	TerminateProcess.errcheck = errcheck
+
+
+# Do not show error messages due to errors in the program being tested
+try:
+	errmode = windll.kernel32.GetErrorMode()
+except AttributeError:
+	# GetErrorMode is available on Vista/2008 and up
+	errmode = windll.kernel32.SetErrorMode(0)
+windll.kernel32.SetErrorMode(errmode | 0x8003)
+
+stdin = GetStdHandle(STD_INPUT_HANDLE)
+try:
+	SetConsoleMode(stdin, ENABLE_PROCESSED_INPUT)
+except WindowsError:
+	console_input = False
+else:
+	console_input = True
+	FlushConsoleInputBuffer(stdin)
+
+def call(*args, **kwargs):
+	case = kwargs.pop('case')
+	job = CreateJobObject(None)
+	flags = 0
+	if case.maxcputime:
+		flags |= JOB_OBJECT_LIMIT_PROCESS_TIME
+	if case.maxmemory:
+		flags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY
+	limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION(
+		JOBOBJECT_BASIC_LIMIT_INFORMATION(
+			PerProcessUserTimeLimit=ceil((case.maxcputime or 0)*10000000),
+			LimitFlags=flags,
+		),
+		ProcessMemoryLimit=ceil((case.maxmemory or 0)*1048576),
+	)
+	SetInformationJobObject(job, JobObjectExtendedLimitInformation, limits)
+	try:
+		case.process = Popen(*args, **kwargs)
+	except OSError:
+		raise 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 WallTimeLimitExceeded
+		else:
+			case.process.wait()
+	else:
+		handles = case.process._handle, stdin
+		if case.maxwalltime:
+			time_end = case.time_started + case.maxwalltime
+			while case.process.poll() is None:
+				remaining = time_end - clock()
+				if remaining > 0:
+					if (WaitForMultipleObjects(handles, False, remaining) ==
+					    WAIT_OBJECT_0 + 1):
+						ir = ReadConsoleInput(stdin)
+						if (ir and
+						    ir.EventType == KEY_EVENT and
+						    ir.Event.KeyEvent.bKeyDown and
+						    ir.Event.KeyEvent.wVirtualKeyCode == 27):
+							raise CanceledByUser
+				else:
+					raise WallTimeLimitExceeded
+		else:
+			while case.process.poll() is None:
+				if (WaitForMultipleObjects(handles, False, INFINITE) ==
+				    WAIT_OBJECT_0 + 1):
+					ir = ReadConsoleInput(stdin)
+					if (ir and
+					    ir.EventType == KEY_EVENT and
+					    ir.Event.KeyEvent.bKeyDown and
+					    ir.Event.KeyEvent.wVirtualKeyCode == 27):
+						raise CanceledByUser
+	case.time_stopped = clock()
+	if GetProcessTimes:
+		try:
+			times = GetProcessTimes(case.process._handle)
+		except WindowsError:
+			pass
+		else:
+			if case.maxcputime or not case.maxwalltime:
+				cputime = times.kernel + times.user
+				case.time_stopped = cputime
+				case.time_started = 0
+				case.time_limit_string = case.cpu_time_limit_string
+				if case.maxcputime and cputime > case.maxcputime:
+					raise CPUTimeLimitExceeded
+			else:
+				case.time_stopped = times.exit
+				case.time_started = times.creation
+				walltime = times.exit - times.creation
+				if case.maxwalltime and walltime > case.maxwalltime:
+					raise WallTimeLimitExceeded
+	if case.maxcputime and case.process.returncode == 1816:
+		raise CPUTimeLimitExceeded
+	if case.maxmemory and GetProcessMemoryInfo:
+		try:
+			counters = GetProcessMemoryInfo(case.process._handle)
+		except WindowsError:
+			pass
+		else:
+			if counters.PeakPagefileUsage > case.maxmemory * 1048576:
+				raise MemoryLimitExceeded
+
+
+def kill(process):
+	# Give up after three attempts
+	for i in range(3):
+		try:
+			try:
+				process.terminate()
+			except AttributeError:
+				TerminateProcess(process._handle, 1)
+		except WindowsError:
+			time.sleep(0)
+		else:
+			break
--- a/win32.py	Fri May 27 22:39:46 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,561 +0,0 @@
-# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-
-from compat import *
-import testcases  # mutual import
-
-from ctypes import *
-from ctypes.wintypes import *
-from msvcrt import getch as pause
-import os, subprocess, sys, time
-
-try:
-	from _winreg import *
-except ImportError:
-	from winreg import *
-
-try:
-	from testcases import clock
-except ImportError:
-	from time import clock
-
-# Defaults that may be overwritten by values from _subprocess
-INFINITE = -1
-STD_INPUT_HANDLE = -10
-WAIT_OBJECT_0 = 0
-
-try:
-	from _subprocess import *
-except ImportError:
-	pass
-
-try:
-	from numbers import Integral
-except ImportError:
-	Integral = int, long
-
-try:
-	from collections import namedtuple
-except ImportError:
-	from operator import itemgetter
-	class ProcessTimes(tuple):
-		__slots__ = ()
-		def __new__(cls, creation, exit, kernel, user):
-			return tuple.__new__(cls, (creation, exit, kernel, user))
-		__getnewargs__ = lambda self: tuple(self)
-		creation, exit, kernel, user = map(property, map(itemgetter, range(4)))
-else:
-	ProcessTimes = namedtuple('ProcessTimes', 'creation exit kernel user')
-
-__all__ = 'call', 'kill', 'pause', 'clock'
-
-
-from functools import wraps
-pathext = [''] + os.environ['PATHEXT'].split(';')
-@wraps(subprocess.Popen)
-def Popen(cmdline, *args, **kwargs):
-	try:
-		return subprocess.Popen(cmdline, *args, **kwargs)
-	except WindowsError:
-		for ext in pathext:
-			path = cmdline[0] + ext
-			newcmdline = type(cmdline)((path,)) + cmdline[1:]
-			try:
-				return subprocess.Popen(newcmdline, *args, **kwargs)
-			except WindowsError:
-				pass
-			for branch in HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE:
-				try:
-					path = (R'SOFTWARE\Microsoft\Windows\CurrentVersion'
-					        R'\App Paths\%s%s' % (cmdline[0], ext))
-					path = QueryValue(branch, path)
-					break
-				except WindowsError:
-					pass
-			else:
-				continue
-			if path[0] == '"' == path[-1]:
-				path = path[1:-1]
-			newcmdline = type(cmdline)((path,)) + cmdline[1:]
-			try:
-				return subprocess.Popen(newcmdline, *args, **kwargs)
-			except WindowsError:
-				pass
-		# I'd like to transparently re-raise the exception generated
-		# on the very first try, but syntax differences preclude me from
-		# doing so in Python 2 and it can't be done at all in Python 3
-		raise
-
-
-# Automatically convert _subprocess handle objects into low-level
-# HANDLEs and replicate their functionality for our own use
-try:
-	_subprocess_handle = type(GetCurrentProcess())
-except NameError:
-	_subprocess_handle = Integral
-class Handle(object):
-	@staticmethod
-	def from_param(handle):
-		if isinstance(handle, (_subprocess_handle, Integral)):
-			return HANDLE(int(handle))
-		elif isinstance(handle, Handle):
-			return HANDLE(handle.handle)
-		elif isinstance(handle, HANDLE):
-			return handle
-		else:
-			raise TypeError('cannot convert %s to a handle' %
-			                type(handle).__name__)
-	
-	__slots__ = 'handle'
-	
-	def __init__(self, handle):
-		if isinstance(handle, Integral):
-			self.handle = handle
-		elif isinstance(handle, HANDLE):
-			self.handle = handle.value
-		elif isinstance(handle, Handle):
-			self.handle = handle.handle
-		elif isinstance(handle, _subprocess_handle):
-			handle = HANDLE(int(handle))
-			flags = DWORD()
-			try:
-				if windll.kernel32.GetHandleInformation(handle, byref(flags)):
-					flags = flags.value
-				else:
-					flags = 0
-			except AttributeError:
-				# Available on NT 3.51 and up, NT line only
-				flags = 0
-			proc = HANDLE(int(GetCurrentProcess()))
-			handle = DuplicateHandle(proc, handle, proc, 0, flags & 1, 2)
-			self.handle = handle.Detach()
-		else:
-			raise TypeError("Handle() argument must be a handle, not '%s'" %
-			                type(handle).__name__)
-	
-	def __int__(self):
-		return int(self.handle)
-	
-	def Detach(self):
-		handle = self.handle
-		self.handle = None
-		return handle
-	
-	# This is also __del__, so only locals are accessed
-	def Close(self, _CloseHandle=windll.kernel32.CloseHandle, _HANDLE=HANDLE):
-		if getattr(self, 'handle', None):
-			_CloseHandle(_HANDLE(self.handle))
-			self.handle = None
-	__del__ = Close
-
-CHAR = c_char
-INVALID_HANDLE_VALUE = HANDLE(-1).value
-LPDWORD = POINTER(DWORD)
-LPFILETIME = POINTER(FILETIME)
-SIZE_T = ULONG_PTR = WPARAM
-ULONGLONG = c_ulonglong
-
-try:
-	unicode
-except NameError:
-	LPCTSTR = LPCWSTR
-	UNISUFFIX = 'W'
-else:
-	LPCTSTR = LPCSTR
-	UNISUFFIX = 'A'
-
-
-prototype = WINFUNCTYPE(BOOL, Handle,
-                        LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME)
-flags = ((1, 'process'),
-         (2, 'creation'), (2, 'exit'), (2, 'kernel'), (2, 'user'))
-try:
-	GetProcessTimes = prototype(('GetProcessTimes', windll.kernel32), flags)
-except AttributeError:
-	# Available on NT 3.5 and up, NT line only
-	GetProcessTimes = None
-else:
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-		times = ((t.dwHighDateTime << 32 | t.dwLowDateTime) / 10000000
-		         for t in args[1:])
-		return ProcessTimes(*times)
-	GetProcessTimes.errcheck = errcheck
-
-
-class PROCESS_MEMORY_COUNTERS(Structure):
-	_fields_ = (('cb', DWORD),
-	            ('PageFaultCount', DWORD),
-	            ('PeakWorkingSetSize', SIZE_T),
-	            ('WorkingSetSize', SIZE_T),
-	            ('QuotaPeakPagedPoolUsage', SIZE_T),
-	            ('QuotaPagedPoolUsage', SIZE_T),
-	            ('QuotaPeakNonPagedPoolUsage', SIZE_T),
-	            ('QuotaNonPagedPoolUsage', SIZE_T),
-	            ('PagefileUsage', SIZE_T),
-	            ('PeakPagefileUsage', SIZE_T))
-
-prototype = WINFUNCTYPE(BOOL, Handle, POINTER(PROCESS_MEMORY_COUNTERS), DWORD)
-flags = ((1, 'process'), (2, 'counters'),
-         (5, 'cb', sizeof(PROCESS_MEMORY_COUNTERS)))
-try:
-	GetProcessMemoryInfo = prototype(('GetProcessMemoryInfo', windll.psapi),
-	                                 flags)
-except AttributeError:
-	# Available on NT 4.0 and up, NT line only
-	GetProcessMemoryInfo = None
-else:
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-		return args
-	GetProcessMemoryInfo.errcheck = errcheck
-
-
-class _uChar_union(Union):
-	_fields_ = (('UnicodeChar', WCHAR),
-	            ('AsciiChar', CHAR))
-
-class KEY_EVENT_RECORD(Structure):
-	_fields_ = (('bKeyDown', BOOL),
-	            ('wRepeatCount', WORD),
-	            ('wVirtualKeyCode', WORD),
-	            ('wVirtualScanCode', WORD),
-	            ('uChar', _uChar_union),
-	            ('dwControlKeyState', DWORD))
-
-RIGHT_ALT_PRESSED  = 0x001
-LEFT_ALT_PRESSED   = 0x002
-RIGHT_CTRL_PRESSED = 0x004
-LEFT_CTRL_PRESSED  = 0x008
-SHIFT_PRESSED      = 0x010
-NUMLOCK_ON         = 0x020
-SCROLLLOCK_ON      = 0x040
-CAPSLOCK_ON        = 0x080
-ENHANCED_KEY       = 0x100
-
-class _Event_union(Union):
-	_fields_ = ('KeyEvent', KEY_EVENT_RECORD),
-
-class INPUT_RECORD(Structure):
-	_fields_ = (('EventType', WORD),
-	            ('Event', _Event_union))
-
-KEY_EVENT                = 0x01
-MOUSE_EVENT              = 0x02
-WINDOW_BUFFER_SIZE_EVENT = 0x04
-MENU_EVENT               = 0x08
-FOCUS_EVENT              = 0x10
-
-prototype = WINFUNCTYPE(BOOL, Handle, POINTER(INPUT_RECORD), DWORD, LPDWORD)
-flags = (1, 'input'), (2, 'buffer'), (5, 'length', 1), (2, 'number_read')
-ReadConsoleInput = prototype(('ReadConsoleInputA', windll.kernel32), flags)
-def errcheck(result, func, args):
-	if not result: raise WinError()
-	return args[1] if args[3] else None
-ReadConsoleInput.errcheck = errcheck
-
-
-prototype = WINFUNCTYPE(BOOL, Handle)
-flags = (1, 'input'),
-FlushConsoleInputBuffer = prototype(('FlushConsoleInputBuffer',
-                                     windll.kernel32), flags)
-def errcheck(result, func, args):
-	if not result: raise WinError()
-FlushConsoleInputBuffer.errcheck = errcheck
-
-
-prototype = WINFUNCTYPE(BOOL, Handle, DWORD)
-flags = (1, 'console'), (1, 'mode')
-SetConsoleMode = prototype(('SetConsoleMode', windll.kernel32), flags)
-def errcheck(result, func, args):
-	if not result: raise WinError()
-SetConsoleMode.errcheck = errcheck
-
-ENABLE_PROCESSED_INPUT = 0x001
-ENABLE_LINE_INPUT      = 0x002
-ENABLE_ECHO_INPUT      = 0x004
-ENABLE_WINDOW_INPUT    = 0x008
-ENABLE_MOUSE_INPUT     = 0x010
-ENABLE_INSERT_MODE     = 0x020
-ENABLE_QUICK_EDIT_MODE = 0x040
-ENABLE_EXTENDED_FLAGS  = 0x080
-
-ENABLE_PROCESSED_OUTPUT   = 1
-ENABLE_WRAP_AT_EOL_OUTPUT = 2
-
-
-prototype = WINFUNCTYPE(HANDLE, c_void_p, LPCTSTR)
-flags = (5, 'attributes'), (1, 'name')
-try:
-	CreateJobObject = prototype(('CreateJobObject'+UNISUFFIX, windll.kernel32),
-	                            flags)
-except AttributeError:
-	# Available on 2000 and up, NT line only
-	CreateJobObject = lambda name: None
-else:
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-		return Handle(result)
-	CreateJobObject.errcheck = errcheck
-
-
-prototype = WINFUNCTYPE(BOOL, Handle, Handle)
-flags = (1, 'job'), (1, 'handle')
-try:
-	AssignProcessToJobObject = prototype(('AssignProcessToJobObject',
-	                                      windll.kernel32), flags)
-except AttributeError:
-	# Available on 2000 and up, NT line only
-	AssignProcessToJobObject = lambda job, handle: None
-else:
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-	AssignProcessToJobObject.errcheck = errcheck
-
-
-class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure):
-	_fields_ = (('PerProcessUserTimeLimit', LARGE_INTEGER),
-	            ('PerJobUserTimeLimit', LARGE_INTEGER),
-	            ('LimitFlags', DWORD),
-	            ('MinimumWorkingSetSize', SIZE_T),
-	            ('MaximumWorkingSetSize', SIZE_T),
-	            ('ActiveProcessLimit', DWORD),
-	            ('Affinity', ULONG_PTR),
-	            ('PriorityClass', DWORD),
-	            ('SchedulingClass', DWORD))
-
-JOB_OBJECT_LIMIT_WORKINGSET                 = 0x0001
-JOB_OBJECT_LIMIT_PROCESS_TIME               = 0x0002
-JOB_OBJECT_LIMIT_JOB_TIME                   = 0x0004
-JOB_OBJECT_LIMIT_ACTIVE_PROCESS             = 0x0008
-JOB_OBJECT_LIMIT_AFFINITY                   = 0x0010
-JOB_OBJECT_LIMIT_PRIORITY_CLASS             = 0x0020
-JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME          = 0x0040
-JOB_OBJECT_LIMIT_SCHEDULING_CLASS           = 0x0080
-JOB_OBJECT_LIMIT_PROCESS_MEMORY             = 0x0100
-JOB_OBJECT_LIMIT_JOB_MEMORY                 = 0x0200
-JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x0400
-JOB_OBJECT_LIMIT_BREAKAWAY_OK               = 0x0800
-JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK        = 0x1000
-JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE          = 0x2000
-JOB_OBJECT_LIMIT_SUBSET_AFFINITY            = 0x4000
-
-class IO_COUNTERS(Structure):
-	_fields_ = (('ReadOperationCount', ULONGLONG),
-	            ('WriteOperationCount', ULONGLONG),
-	            ('OtherOperationCount', ULONGLONG),
-	            ('ReadTransferCount', ULONGLONG),
-	            ('WriteTransferCount', ULONGLONG),
-	            ('OtherTransferCount', ULONGLONG))
-
-class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure):
-	_fields_ = (('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION),
-	            ('IoInfo', IO_COUNTERS),
-	            ('ProcessMemoryLimit', SIZE_T),
-	            ('JobMemoryLimit', SIZE_T),
-	            ('PeakProcessMemoryUsed', SIZE_T),
-	            ('PeakJobMemoryUsed', SIZE_T))
-
-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)
-except AttributeError:
-	# Available on 2000 and up, NT line only
-	SetInformationJobObject = lambda job, infoclass, info: None
-else:
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-	_setjobinfo.errcheck = errcheck
-	def SetInformationJobObject(job, infoclass, info):
-		return _setjobinfo(job, infoclass, byref(info), sizeof(info))
-
-(
-	JobObjectBasicAccountingInformation,
-	JobObjectBasicLimitInformation,
-	JobObjectBasicProcessIdList,
-	JobObjectBasicUIRestrictions,
-	JobObjectSecurityLimitInformation,   
-	JobObjectEndOfJobTimeInformation,
-	JobObjectAssociateCompletionPortInformation,
-	JobObjectBasicAndIoAccountingInformation,
-	JobObjectExtendedLimitInformation,
-	JobObjectJobSetInformation,
-	MaxJobObjectInfoClass
-) = range(1, 12)
-
-
-prototype = WINFUNCTYPE(DWORD, DWORD, POINTER(HANDLE), BOOL, DWORD)
-flags = (1, 'count'), (1, 'handles'), (1, 'wait_all'), (1, 'milliseconds')
-_wait_multiple = prototype(('WaitForMultipleObjects', windll.kernel32), flags)
-def errcheck(result, func, args):
-	if result == WAIT_FAILED: raise WinError()
-	return args
-_wait_multiple.errcheck = errcheck
-def WaitForMultipleObjects(handles, wait_all, timeout):
-	n = len(handles)
-	handles = (Handle.from_param(handle) for handle in handles)
-	timeout = ceil(timeout * 1000)
-	return _wait_multiple(n, (HANDLE * n)(*handles), wait_all, timeout)
-
-# WAIT_OBJECT_0 defined at the top of the file
-WAIT_ABANDONED_0 = 0x00000080
-WAIT_TIMEOUT     = 0x00000102
-WAIT_FAILED      = 0xFFFFFFFF
-
-
-try:
-	_wait_single = WaitForSingleObject
-except NameError:
-	prototype = WINFUNCTYPE(DWORD, Handle, DWORD)
-	flags = (1, 'handle'), (1, 'milliseconds')
-	_wait_single = prototype(('WaitForSingleObject', windll.kernel32), flags)
-	def errcheck(result, func, args):
-		if result == WAIT_FAILED: raise WinError()
-		return args
-	_wait_single.errcheck = errcheck
-def WaitForSingleObject(handle, timeout):
-	return _wait_single(handle, ceil(timeout * 1000))
-
-
-try:
-	GetStdHandle
-except NameError:
-	prototype = WINFUNCTYPE(HANDLE, DWORD)
-	flags = (1, 'which'),
-	GetStdHandle = prototype(('GetStdHandle', windll.kernel32), flags)
-	def errcheck(result, func, args):
-		if result == INVALID_HANDLE_VALUE: raise WinError()
-		return args if result else None
-	GetStdHandle.errcheck = errcheck
-
-
-try:
-	TerminateProcess
-except NameError:
-	prototype = WINFUNCTYPE(BOOL, Handle, UINT)
-	flags = (1, 'process'), (1, 'exitcode')
-	TerminateProcess = prototype(('TerminateProcess', windll.kernel32), flags)
-	def errcheck(result, func, args):
-		if not result: raise WinError()
-	TerminateProcess.errcheck = errcheck
-
-
-# Do not show error messages due to errors in the program being tested
-try:
-	errmode = windll.kernel32.GetErrorMode()
-except AttributeError:
-	# GetErrorMode is available on Vista/2008 and up
-	errmode = windll.kernel32.SetErrorMode(0)
-windll.kernel32.SetErrorMode(errmode | 0x8003)
-
-stdin = GetStdHandle(STD_INPUT_HANDLE)
-try:
-	SetConsoleMode(stdin, ENABLE_PROCESSED_INPUT)
-except WindowsError:
-	console_input = False
-else:
-	console_input = True
-	FlushConsoleInputBuffer(stdin)
-
-def call(*args, **kwargs):
-	case = kwargs.pop('case')
-	job = CreateJobObject(None)
-	flags = 0
-	if case.maxcputime:
-		flags |= JOB_OBJECT_LIMIT_PROCESS_TIME
-	if case.maxmemory:
-		flags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY
-	limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION(
-		JOBOBJECT_BASIC_LIMIT_INFORMATION(
-			PerProcessUserTimeLimit=ceil((case.maxcputime or 0)*10000000),
-			LimitFlags=flags,
-		),
-		ProcessMemoryLimit=ceil((case.maxmemory or 0)*1048576),
-	)
-	SetInformationJobObject(job, JobObjectExtendedLimitInformation, limits)
-	try:
-		case.process = Popen(*args, **kwargs)
-	except OSError:
-		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 testcases.WallTimeLimitExceeded
-		else:
-			case.process.wait()
-	else:
-		handles = case.process._handle, stdin
-		if case.maxwalltime:
-			time_end = case.time_started + case.maxwalltime
-			while case.process.poll() is None:
-				remaining = time_end - clock()
-				if remaining > 0:
-					if (WaitForMultipleObjects(handles, False, remaining) ==
-					    WAIT_OBJECT_0 + 1):
-						ir = ReadConsoleInput(stdin)
-						if (ir and
-						    ir.EventType == KEY_EVENT and
-						    ir.Event.KeyEvent.bKeyDown and
-						    ir.Event.KeyEvent.wVirtualKeyCode == 27):
-							raise testcases.CanceledByUser
-				else:
-					raise testcases.WallTimeLimitExceeded
-		else:
-			while case.process.poll() is None:
-				if (WaitForMultipleObjects(handles, False, INFINITE) ==
-				    WAIT_OBJECT_0 + 1):
-					ir = ReadConsoleInput(stdin)
-					if (ir and
-					    ir.EventType == KEY_EVENT and
-					    ir.Event.KeyEvent.bKeyDown and
-					    ir.Event.KeyEvent.wVirtualKeyCode == 27):
-						raise testcases.CanceledByUser
-	case.time_stopped = clock()
-	if GetProcessTimes:
-		try:
-			times = GetProcessTimes(case.process._handle)
-		except WindowsError:
-			pass
-		else:
-			if case.maxcputime or not case.maxwalltime:
-				cputime = times.kernel + times.user
-				case.time_stopped = cputime
-				case.time_started = 0
-				case.time_limit_string = case.cpu_time_limit_string
-				if case.maxcputime and cputime > case.maxcputime:
-					raise testcases.CPUTimeLimitExceeded
-			else:
-				case.time_stopped = times.exit
-				case.time_started = times.creation
-				walltime = times.exit - times.creation
-				if case.maxwalltime and walltime > case.maxwalltime:
-					raise testcases.WallTimeLimitExceeded
-	if case.maxcputime and case.process.returncode == 1816:
-		raise testcases.CPUTimeLimitExceeded
-	if case.maxmemory and GetProcessMemoryInfo:
-		try:
-			counters = GetProcessMemoryInfo(case.process._handle)
-		except WindowsError:
-			pass
-		else:
-			if counters.PeakPagefileUsage > case.maxmemory * 1048576:
-				raise testcases.MemoryLimitExceeded
-
-
-def kill(process):
-	# Give up after three attempts
-	for i in range(3):
-		try:
-			try:
-				process.terminate()
-			except AttributeError:
-				TerminateProcess(process._handle, 1)
-		except WindowsError:
-			time.sleep(0)
-		else:
-			break