# HG changeset patch # User Oleg Oshmyan # Date 1306589065 -3600 # Node ID d5b6708c19552e08dd284ef705b15cb657a756dd # Parent d2c266c8d82031bdd3c25dafd4e8de6f01a82b75 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. diff -r d2c266c8d820 -r d5b6708c1955 _unixmodule.cpp --- 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 - -#include -#include - -#ifdef HAVE_SYS_TYPES_H -#include -#endif - -#ifdef HAVE_FCNTL_H -#include -#endif - -#include - -#ifdef HAVE_SIGNAL_H -#include -#endif - -#ifdef HAVE_SPAWN_H -#include -#ifdef __APPLE__ -#pragma weak_import posix_spawnp -#endif -#endif - -#ifdef HAVE_SYS_RESOURCE_H -#include -#endif - -#ifdef HAVE_SYS_WAIT_H -#include -#endif - -#ifdef HAVE_TERMIOS_H -#include -#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 -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 -} diff -r d2c266c8d820 -r d5b6708c1955 compat.py --- 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 - -# 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 diff -r d2c266c8d820 -r d5b6708c1955 config.py --- 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 - -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 diff -r d2c266c8d820 -r d5b6708c1955 files.py --- 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 - -"""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 diff -r d2c266c8d820 -r d5b6708c1955 problem.py --- 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 - -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 diff -r d2c266c8d820 -r d5b6708c1955 publish.sh --- 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 diff -r d2c266c8d820 -r d5b6708c1955 setup.py --- /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}, + ) diff -r d2c266c8d820 -r d5b6708c1955 testcases.py --- 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 - -# 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 diff -r d2c266c8d820 -r d5b6708c1955 unix.py --- 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 - -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) diff -r d2c266c8d820 -r d5b6708c1955 upreckon-vcs --- 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 - -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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon.cmd --- 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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/__init__.py diff -r d2c266c8d820 -r d5b6708c1955 upreckon/_unixmodule.cpp --- /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 + +#include +#include +#include + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include + +#ifdef HAVE_SIGNAL_H +#include +#endif + +#ifdef HAVE_SPAWN_H +#include +#ifdef __APPLE__ +#pragma weak_import posix_spawnp +#endif +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef HAVE_TERMIOS_H +#include +#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 +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 +} diff -r d2c266c8d820 -r d5b6708c1955 upreckon/compat.py --- /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 + +# 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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/config.py --- /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 + +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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/exceptions.py --- /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 + +__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__ = () diff -r d2c266c8d820 -r d5b6708c1955 upreckon/files.py --- /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 + +"""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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/problem.py --- /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 + +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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/publish.sh --- /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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/testcases.py --- /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 + +# 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. diff -r d2c266c8d820 -r d5b6708c1955 upreckon/unix.py --- /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 + +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) diff -r d2c266c8d820 -r d5b6708c1955 upreckon/upreckon-vcs --- /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 + +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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/upreckon.cmd --- /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 diff -r d2c266c8d820 -r d5b6708c1955 upreckon/win32.py --- /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 + +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 diff -r d2c266c8d820 -r d5b6708c1955 win32.py --- 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 - -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