Mercurial > ~astiob > upreckon > hgweb
view test.py @ 4:06eef313aeaa
-c command line option fixes
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Sat, 13 Feb 2010 23:40:53 +0000 |
parents | cd4304ff1d1b |
children | eb15a5a9b026 |
line wrap: on
line source
#! /usr/bin/python # Copyright (c) 2009-2010 Chortos-2 <chortos@inbox.lv> import os, sys, shutil, time, subprocess, filecmp, optparse, signal, tempfile, tarfile, zipfile parser = optparse.OptionParser(version='test.py 1.21.0 (SVN)', usage='usage: %prog [options] [problem names] [[path' + os.path.sep + 'to' + os.path.sep + ']solution-app] [test case numbers]\n\nTest case numbers can be specified in plain text or as a Python expression\nif there is only one positional argument.\n\nOnly problem names listed in testconf.py are recognized.') parser.add_option('-e', '--exclude', dest='exclude', action='append', help='test case number(s) to exclude, as a Python expression; multiple -e options can be supplied') parser.add_option('-c', '--cleanup', dest='clean', action='store_true', default=False, help='delete the copies of input/output files and exit') 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('-m', '--copy-io', dest='copyonly', action='store_true', default=False, help='only create a copy of the input/output files of the last test case for manual testing; to delete them, use options -cs or -cm') parser.add_option('-x', '--auto-exit', dest='pause', action='store_false', default=True, help='do not wait for a key to be pressed when finished testing') parser.add_option('-p', '--python', action='store_true', default=False, help='always parse all positional arguments as a single Python expression (including the first argument even if it names an executable file)') parser.add_option('-t', '--detect-time', dest='autotime', action='store_true', default=False, help='spend a second detecting the most precise time measurement function') options, args = parser.parse_args() parser.destroy() del parser globals1 = set(globals()) # Initialize some configuration variables with default values tasknames = (os.path.curdir,) maxtime = 0 tests = () dummies = () testsexcluded = () padwithzeroestolength = 0 taskweight = 100 pointmap = {} stdio = False dummyinname = '' dummyoutname = '' tester = '' def exectestconf_helper(name): if os.path.isfile('tests.tar'): f = tarfile.open('tests.tar') try: exec f.extractfile(name).read() in globals() f.close() return True except KeyError: f.close() if os.path.isfile('tests.zip'): f = zipfile.ZipFile('tests.zip') try: exec f.open(name, 'rU').read() in globals() f.close() return True except KeyError: f.close() if os.path.isfile('tests.tgz'): f = tarfile.open('tests.tgz') try: exec f.extractfile(name).read() in globals() f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.gz'): f = tarfile.open('tests.tar.gz') try: exec f.extractfile(name).read() in globals() f.close() return True except KeyError: f.close() if os.path.isfile('tests.tbz2'): f = tarfile.open('tests.tbz2') try: exec f.extractfile(name).read() in globals() f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.bz2'): f = tarfile.open('tests.tar.bz2') try: exec f.extractfile(name).read() in globals() f.close() return True except KeyError: f.close() return False try: execfile('testconf.py') except IOError, error: exc_info = sys.exc_info()[2] try: execfile(os.path.join('tests', 'testconf.py')) except IOError: if not exectestconf_helper('testconf.py'): raise IOError, (error.errno, 'The configuration file is missing', error.filename), exc_info del exc_info globals2 = set(globals()) globals2.remove('globals1') globals2 -= globals1 del globals1 shared = {} g = globals() for k in globals2: shared[k] = g[k] newtasknames = [] while len(args) and args[0] in tasknames: newtasknames.append(args[0]) del args[0] if len(newtasknames): tasknames = newtasknames scoresumoveralltasks = 0 scoremaxoveralltasks = 0 ntasks = 0 nfulltasks = 0 cwd = '' # At any time this is either '' or taskname if options.autotime: c = time.clock() time.sleep(1) c = time.clock() - c if int(c + .99999) == 1: clock = time.clock else: clock = time.time elif os.name == 'nt': clock = time.clock else: clock = time.time if options.copyonly: options.erase = False def existstestcase_helper(name): if os.path.isfile('tests.tar'): f = tarfile.open('tests.tar') try: f.getmember(name) f.close() return True except KeyError: f.close() if os.path.isfile('tests.zip'): f = zipfile.ZipFile('tests.zip') try: f.getinfo(name) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tgz'): f = tarfile.open('tests.tgz') try: f.getmember(name) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.gz'): f = tarfile.open('tests.tar.gz') try: f.getmember(name) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tbz2'): f = tarfile.open('tests.tbz2') try: f.getmember(name) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.bz2'): f = tarfile.open('tests.tar.bz2') try: f.getmember(name) f.close() return True except KeyError: f.close() return False def existstestcase(name): if os.path.isfile(os.path.join('tests', taskname, name)) or os.path.isfile(os.path.join('tests', name)): return True if cwd and (os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)) or os.path.isfile(os.path.join(oldcwd, 'tests', name))): return True if existstestcase_helper(os.path.join(taskname, name)) or existstestcase_helper(name): return True if cwd: os.chdir(oldcwd) if existstestcase_helper(os.path.join(cwd, name)) or existstestcase_helper(name): os.chdir(cwd) return True os.chdir(cwd) return False def opentestcase_helper(name): if os.path.isfile('tests.tar'): f = tarfile.open('tests.tar') try: c = f.extractfile(name) return c except KeyError: f.close() if os.path.isfile('tests.zip'): f = zipfile.ZipFile('tests.zip') try: c = f.open(name, 'rU') f.close() return c except KeyError: f.close() if os.path.isfile('tests.tgz'): f = tarfile.open('tests.tgz') try: c = f.extractfile(name) return c except KeyError: f.close() if os.path.isfile('tests.tar.gz'): f = tarfile.open('tests.tar.gz') try: c = f.extractfile(name) return c except KeyError: f.close() if os.path.isfile('tests.tbz2'): f = tarfile.open('tests.tbz2') try: c = f.extractfile(name) return c except KeyError: f.close() if os.path.isfile('tests.tar.bz2'): f = tarfile.open('tests.tar.bz2') try: c = f.extractfile(name) return c except KeyError: f.close() return None def opentestcase(name): if os.path.isfile(os.path.join('tests', taskname, name)): return open(os.path.join('tests', taskname, name), 'rU') elif os.path.isfile(os.path.join('tests', name)): return open(os.path.join('tests', name), 'rU') f = opentestcase_helper(os.path.join(taskname, name)) if not f: f = opentestcase_helper(name) if f: return f if cwd: if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)): return open(os.path.join(oldcwd, 'tests', cwd, name), 'rU') elif os.path.isfile(os.path.join(oldcwd, 'tests', name)): return open(os.path.join(oldcwd, 'tests', name), 'rU') os.chdir(oldcwd) f = opentestcase_helper(os.path.join(cwd, name)) if not f: f = opentestcase_helper(name) os.chdir(cwd) if f: return f raise KeyError, 'The test-case-defining file \'' + name + '\' cannot be found' def copytestcase_helper(name, target): if os.path.isfile('tests.tar'): f = tarfile.open('tests.tar') try: m = f.getmember(name) m.name = target f.extract(m) f.close() return True except KeyError: f.close() if os.path.isfile('tests.zip'): if not os.path.isabs(target): f = zipfile.ZipFile('tests.zip') m = f.getinfo(name) try: m.filename = target f.extract(m) f.close() return True except KeyError: f.close() else: oldcwd = os.getcwdu() os.chdir('/') # FIXME: portability? f = zipfile.ZipFile(os.path.join(oldcwd, 'tests.zip')) try: m = f.getinfo(name) m.filename = target[1:] f.extract(m) f.close() os.chdir(oldcwd) return True except KeyError: f.close() os.chdir(oldwcd) if os.path.isfile('tests.tgz'): f = tarfile.open('tests.tgz') try: m = f.getmember(name) m.name = target f.extract(m) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.gz'): f = tarfile.open('tests.tar.gz') try: m = f.getmember(name) m.name = target f.extract(m) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tbz2'): f = tarfile.open('tests.tbz2') try: m = f.getmember(name) m.name = target f.extract(m) f.close() return True except KeyError: f.close() if os.path.isfile('tests.tar.bz2'): f = tarfile.open('tests.tar.bz2') try: m = f.getmember(name) m.name = target f.extract(m) f.close() return True except KeyError: f.close() return False def copytestcase(name, target): if os.path.isfile(os.path.join('tests', taskname, name)): shutil.copyfile(os.path.join('tests', taskname, name), target) return elif os.path.isfile(os.path.join('tests', name)): shutil.copyfile(os.path.join('tests', name), target) return if copytestcase_helper(os.path.join(taskname, name), target) or copytestcase_helper(name, target): return if cwd: if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)): shutil.copyfile(os.path.join(oldcwd, 'tests', cwd, name), target) return elif os.path.isfile(os.path.join(oldcwd, 'tests', name)): shutil.copyfile(os.path.join(oldcwd, 'tests', name), target) return os.chdir(oldcwd) if copytestcase_helper(os.path.join(cwd, name), target) or copytestcase_helper(name, target): os.chdir(cwd) return os.chdir(cwd) raise KeyError, 'The test-case-defining file \'' + name + '\' cannot be found' # Always chdir if the directory exists but use any existing config def chdir_and_exec_testconf(): global cwd cwd = '' if os.path.isdir(taskname): os.chdir(taskname) if taskname != os.path.curdir: cwd = taskname try: execfile('testconf.py', globals()) return except IOError: pass if not cwd: if os.path.isfile(os.path.join('tests', taskname, 'testconf.py')): execfile(os.path.join('tests', taskname, 'testconf.py'), globals()) return if os.path.isfile(os.path.join('tests', 'testconf.py')): execfile(os.path.join('tests', 'testconf.py'), globals()) return if exectestconf_helper(os.path.join(taskname, 'testconf.py')) or exectestconf_helper('testconf.py'): return if cwd: os.chdir(oldcwd) if os.path.isfile(os.path.join('tests', cwd, 'testconf.py')): execfile(os.path.join('tests', cwd, 'testconf.py'), globals()) os.chdir(cwd) return if os.path.isfile(os.path.join('tests', 'testconf.py')): execfile(os.path.join('tests', 'testconf.py'), globals()) os.chdir(cwd) return if exectestconf_helper(os.path.join(cwd, 'testconf.py')) or exectestconf_helper('testconf.py'): os.chdir(cwd) return if os.path.isfile('testconf.py'): execfile('testconf.py', globals()) os.chdir(cwd) return os.chdir(cwd) elif os.path.isfile('testconf.py'): execfile('testconf.py', globals()) return raise KeyError, 'The configuration file for task ' + taskname + ' is missing' try: name namedefined = True except Exception: namedefined = False for taskname in tasknames: if ntasks: print try: if len(tasknames) > 1: print taskname except Exception: if taskname != os.path.curdir or ntasks: print taskname try: del inname except NameError: pass try: del outname except NameError: pass try: del ansname except NameError: pass if not namedefined and taskname != os.path.curdir: name = os.path.join(os.path.curdir, taskname) for k in shared: g[k] = shared[k] oldcwd = os.getcwdu() chdir_and_exec_testconf() if options.clean: try: if not stdio or tester: if not tester: inname outname if tester: ansname except NameError, error: raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2] if not options.erase: try: inname = inname.replace('%', taskname) except NameError: inname = taskname + '.in' try: outname = outname.replace('%', taskname) except NameError: outname = taskname + '.out' try: ansname = ansname.replace('%', taskname) except NameError: ansname = taskname + '.ans' elif not stdio or tester or not options.erase: inname = inname.replace('%', taskname) outname = outname.replace('%', taskname) if tester: ansname = ansname.replace('%', taskname) if not stdio or tester or not options.erase: if os.path.exists(inname): os.remove(inname) if os.path.exists(outname): os.remove(outname) if (tester or not options.erase) and ansname: if os.path.exists(ansname): os.remove(ansname) continue try: name except NameError, error: if str(error).count('name') == 1: raise NameError, 'configuration ' + str(error), sys.exc_info()[2] else: raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2] try: if not stdio: inname outname testcaseinname if tester: outname if ansname: testcaseoutname else: testcaseoutname except NameError, error: raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2] if not options.erase: try: inname except NameError: inname = taskname + '.in' try: outname except NameError: outname = taskname + '.out' try: ansname except NameError: ansname = taskname + '.ans' if options.pause: try: pause except NameError, error: if os.name == 'posix': pause = 'read -s -n 1' print 'Configuration ' + str(error).replace('name ', 'variable ') + '; it was devised automatically but the choice might be incorrect, so test.py might exit immediately after the testing is completed.' elif os.name == 'nt': pause = 'pause' else: raise NameError, 'configuration ' + str(error).replace('name ', 'variable ') + ' and cannot be devised automatically', sys.exc_info()[2] if not dummyinname: dummyinname = testcaseinname if not dummyoutname and (not tester or ansname): dummyoutname = testcaseoutname dummyinname = dummyinname.replace('%', taskname) dummyoutname = dummyoutname.replace('%', taskname) testcaseinname = testcaseinname.replace('%', taskname) if not stdio or not options.erase: inname = inname.replace('%', taskname) outname = outname.replace('%', taskname) try: ansname = ansname.replace('%', taskname) except NameError: pass if tester: try: inname = inname.replace('%', taskname) except NameError: pass outname = outname.replace('%', taskname) if ansname: ansname = ansname.replace('%', taskname) testcaseoutname = testcaseoutname.replace('%', taskname) else: testcaseoutname = testcaseoutname.replace('%', taskname) if isinstance(padwithzeroestolength, tuple): padwithzeroestolength, paddummieswithzeroestolength = padwithzeroestolength else: paddummieswithzeroestolength = padwithzeroestolength if options.python: dummies = () s = ' '.join(args) tests = eval(s) try: tests.__iter__ except AttributeError: tests = (tests,) elif len(args): if os.path.exists(args[0]): name = args[0] del args[0] if len(args) > 1: dummies = () tests = args elif len(args): dummies = () s = args[0] if len(s) < padwithzeroestolength: s = s.zfill(padwithzeroestolength) if existstestcase(testcaseinname.replace('$', s)): tests = (s,) else: try: tests = eval(args[0]) try: tests.__iter__ except AttributeError: tests = (tests,) except Exception: tests = (s,) if options.exclude: testsexcluded = [] for i in options.exclude: v = eval(i) try: testsexcluded.extend(v) except TypeError: testsexcluded.append(v) # Windows doesn't like paths beginning with .\ and not ending with an extension name = os.path.normcase(name) if os.name == 'nt' and name.startswith('.\\'): name = name[2:] newpointmap = {} for i in pointmap: try: for j in i: newpointmap[j] = pointmap[i] except TypeError: newpointmap[i] = pointmap[i] pointmap = newpointmap if maxtime > 0: strmaxtime = '/%.3f' % maxtime else: strmaxtime = '' padoutputtolength = 0 ntests = [] for j in dummies: try: j.__iter__ except AttributeError: j = (j,) ntests.append((j, True)) for i in j: s = str(i) if len(s) < paddummieswithzeroestolength: s = s.zfill(paddummieswithzeroestolength) s = 'sample ' + s if padoutputtolength < len(s): padoutputtolength = len(s) for j in tests: try: j.__iter__ except AttributeError: j = (j,) ntests.append((j, False)) for i in j: s = str(i) if len(s) < padwithzeroestolength: s = s.zfill(padwithzeroestolength) if padoutputtolength < len(s): padoutputtolength = len(s) tests = ntests score = maxpoints = ncorrect = ntotal = ncorrectvalued = nvalued = 0 if options.copyonly: j, isdummy = tests[-1] if isdummy: realinname = dummyinname realoutname = dummyoutname else: realinname = testcaseinname realoutname = testcaseoutname for i in j: if i in testsexcluded and not isdummy: continue s = str(i) if isdummy: if len(s) < paddummieswithzeroestolength: s = s.zfill(paddummieswithzeroestolength) else: if len(s) < padwithzeroestolength: s = s.zfill(padwithzeroestolength) copytestcase(realinname.replace('$', s), inname) if ansname: copytestcase(realoutname.replace('$', s), ansname) continue for j, isdummy in tests: ncorrectgrp = 0 ntotalgrp = 0 scoregrp = 0 maxpointsgrp = 0 if isdummy: realinname = dummyinname realoutname = dummyoutname else: realinname = testcaseinname realoutname = testcaseoutname for i in j: if i in testsexcluded and not isdummy: continue ntotalgrp += 1 s = str(i) if isdummy: npoints = 0 if len(s) < paddummieswithzeroestolength: s = s.zfill(paddummieswithzeroestolength) spref = 'sample ' else: npoints = pointmap.get(None, 1) npoints = pointmap.get(i, npoints) maxpointsgrp += npoints if npoints: nvalued += 1 if len(s) < padwithzeroestolength: s = s.zfill(padwithzeroestolength) spref = '' print ' ' * (padoutputtolength - len(spref + s)) + spref + s + ':', sys.stdout.flush() outputdata = open(os.devnull, 'w') if stdio: f = tempfile.NamedTemporaryFile(delete=False) inputdatafname = f.name f.close() copytestcase(realinname.replace('$', s), inputdatafname) inputdata = open(inputdatafname, 'rU') if options.erase: tempoutput = tempfile.TemporaryFile('w+') else: tempoutput = open(outname, 'w+') try: proc = subprocess.Popen(name, stdin=inputdata, stdout=tempoutput, stderr=outputdata, universal_newlines=True) except OSError, error: raise OSError, 'The program to be tested cannot be launched: ' + str(error), sys.exc_info()[2] else: if os.path.exists(outname): os.remove(outname) copytestcase(realinname.replace('$', s), inname) try: proc = subprocess.Popen(name, stdin=outputdata, stdout=outputdata, stderr=outputdata, universal_newlines=True) except OSError, error: raise OSError, 'The program to be tested cannot be launched: ' + str(error), sys.exc_info()[2] cl = clock() if maxtime > 0: while 1: proc.poll() elapsed = clock() - cl if proc.returncode == None: if elapsed >= maxtime: print '%.3f%s s, 0/%d, time limit exceeded' % (elapsed, strmaxtime, npoints) sys.stdout.flush() while proc.returncode == None: try: proc.terminate() except OSError: pass except AttributeError: try: os.kill(proc.pid, signal.SIGTERM) except Exception: pass proc.poll() outputdata.close() if stdio: tempoutput.close() break else: print '%.3f%s s,' % (elapsed, strmaxtime), sys.stdout.flush() elapsed = 0 if stdio: tempoutput.seek(0) lines = tempoutput.readlines() tempoutput.close() break if elapsed >= maxtime: continue else: data = proc.communicate() elapsed = clock() - cl print '%.3f%s s,' % (elapsed, strmaxtime), sys.stdout.flush() if stdio: tempoutput.seek(0) lines = tempoutput.readlines() tempoutput.close() outputdata.close() if stdio: inputdata.close() try: os.unlink(inputdatafname) except Exception: pass if proc.returncode > 0: print '0/%d, non-zero return code %d' % (npoints, proc.returncode) sys.stdout.flush() elif proc.returncode < 0: print '0/%d, terminated by signal %d' % (npoints, -proc.returncode) sys.stdout.flush() else: if not tester: if stdio: outputdata = opentestcase(realoutname.replace('$', s)) r = 0 data = outputdata.read().splitlines(True) if len(lines) != len(data): r = 1 else: for i in zip(lines, data): if i[0] != i[1]: r = 1 break outputdata.close() else: try: inputdata = open(outname, 'rU') except IOError: print '0/%g, output file not created or not readable' % npoints sys.stdout.flush() r = None else: outputdata = opentestcase(realoutname.replace('$', s)) r = 0 lines = inputdata.readlines() data = outputdata.read().splitlines(True) if len(lines) != len(data): r = 1 else: for i in zip(lines, data): if i[0] != i[1]: r = 1 break inputdata.close() outputdata.close() else: if ansname: copytestcase(realoutname.replace('$', s), ansname) if stdio: try: copytestcase(realinname.replace('$', s), inname) except NameError: pass outputdata = open(outname, 'w') outputdata.writelines(lines) outputdata.close() try: proc = subprocess.Popen(tester, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) except OSError, error: raise OSError, 'The tester application cannot be launched: ' + str(error), sys.exc_info()[2] data = proc.communicate() r = proc.returncode if tester and data[0]: data = ''.join((' (', data[0].strip(), ')')) else: data = '' if r: print '0/%g, wrong answer%s' % (npoints, data) sys.stdout.flush() elif r == 0: print '%g/%g, OK%s' % (npoints, npoints, data) sys.stdout.flush() scoregrp += npoints ncorrectgrp += 1 if npoints: ncorrectvalued += 1 if ntotalgrp: if scoregrp < maxpointsgrp: scoregrp = 0 if ntotalgrp > 1: print 'Group total: %d/%d tests; %g/%g points' % (ncorrectgrp, ntotalgrp, scoregrp, maxpointsgrp) sys.stdout.flush() ncorrect += ncorrectgrp ntotal += ntotalgrp score += scoregrp maxpoints += maxpointsgrp if options.erase: if not stdio or tester: if os.path.exists(inname): os.remove(inname) if os.path.exists(outname): os.remove(outname) if tester and ansname: if os.path.exists(ansname): os.remove(ansname) elif stdio: copytestcase(realinname.replace('$', s), inname) copytestcase(realoutname.replace('$', s), ansname) if nvalued != ntotal: print 'Grand total: %d/%d tests (%d/%d valued); %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, ncorrectvalued, nvalued, score, maxpoints, (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0, taskweight) else: print 'Grand total: %d/%d tests; %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, score, maxpoints, (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0, taskweight) scoresumoveralltasks += score*taskweight//maxpoints if maxpoints else 0 scoremaxoveralltasks += taskweight ntasks += 1 nfulltasks += int((score == maxpoints) if maxpoints else (taskweight == 0)) os.chdir(oldcwd) if options.clean or options.copyonly: sys.exit() if ntasks != 1: print print 'Grand grand total: %g/%g weighted points; %d/%d problems solved fully' % (scoresumoveralltasks, scoremaxoveralltasks, nfulltasks, ntasks) if options.pause: print 'Press any key to exit... ', sys.stdout.flush() os.system(pause + ' >' + os.devnull)