Files
commandergenius/project/jni/glshim/test/util/run.py
2015-02-09 20:25:03 +00:00

243 lines
7.2 KiB
Python

import argparse
import jinja2
import os
import signal
import subprocess
import sys
import traceback
from blessings import Terminal
from contextlib import contextmanager
signals = dict((k, v) for v, k in signal.__dict__.iteritems() if v.startswith('SIG'))
term = Terminal()
TEST_ROOT = os.getcwd()
env = jinja2.Environment(
trim_blocks=True,
lstrip_blocks=True,
loader=jinja2.FileSystemLoader(os.path.join(TEST_ROOT, 'util', 'template')),
)
@contextmanager
def chdir(d):
old = os.getcwd()
os.chdir(d)
yield
os.chdir(old)
def shell(*args, **kwargs):
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = p.communicate(kwargs.get('input', ''))
out = '\n'.join(((output[0] or '').strip(), (output[1] or '').strip()))
if p.returncode < 0:
sig = signals.get(-p.returncode)
if sig is not None:
out += '\n' + sig
return out.strip(), p.returncode
def walk(base):
for root, _, files in os.walk(base):
for name in files:
yield os.path.join(root, name)
class Test:
def __init__(self, path, tail):
self.path = path
self.name = os.path.splitext(tail)[0].strip('/')
self.exe = os.path.basename(self.name)
self.dir = self.name.rsplit(self.exe, 1)[0].strip('/')
self.ran = False
self.success = None
self.build_failed = False
self.output = ''
@classmethod
def find(cls, base, filt):
tests = []
for path in walk(base):
tail = path.replace(base, '', 1)
test = None
if path.endswith('.c'):
test = Test(path, tail)
elif path.endswith('.py'):
test = PythonTest(path, tail)
else:
continue
if test and filt:
for f in filt:
if test.name.startswith(f):
break
else:
continue
tests.append(test)
return tests
@property
def status(self):
if self.build_failed:
return 'build failed'
elif not self.ran:
return 'skipped'
return 'pass' if self.success else 'fail'
@property
def status_color(self):
if self.build_failed:
return term.red
elif not self.ran:
return term.yellow
return term.green if self.success else term.red
def build(self, project):
junk_dir = os.path.join(TEST_ROOT, 'build')
bin_dir = os.path.join(TEST_ROOT, 'bin', self.dir)
if not os.path.exists(junk_dir):
os.makedirs(junk_dir)
cmakelists = os.path.join(junk_dir, 'CMakeLists.txt')
t = env.get_template('CMakeLists.txt.j2')
txt = t.render(
project=args.project,
exe=self.exe,
sources=self.path,
bin_dir=bin_dir,
util=os.path.join(TEST_ROOT, 'util'),
)
with open(cmakelists, 'w') as f:
f.write(txt)
out, status = shell('cmake', cmakelists)
if status:
self.output = out
self.build_failed = True
return False
with chdir(junk_dir):
out, status = shell('make', '-j2')
if status:
self.output = out
self.build_failed = True
return False
tmp = os.path.join(bin_dir, 'tmp')
out = os.path.join(bin_dir, self.exe)
if os.path.exists(out):
os.unlink(out)
os.rename(tmp, out)
return True
def run(self):
bin_dir = os.path.join(TEST_ROOT, 'bin', self.dir)
with chdir(bin_dir):
self.output, status = shell('./' + self.exe)
self.ran = True
self.success = (status == 0)
return self.success
def __repr__(self):
if self.ran:
return '<Test: {} ({})>'.format(self.name, self.status)
else:
return '<Test: {}>'.format(self.name)
class PythonTest(Test):
def build(self, project):
return True
def run(self):
with chdir(TEST_ROOT):
self.output, status = shell('python', self.path)
self.ran = True
self.success = (status == 0)
return False
def run(args):
tests = Test.find(args.base, args.tests)
if not tests:
print 'No tests!'
return
step_fmt = lambda step: term.bold('[' + step + ']')
status_fmt = lambda test: term.bold('[' + test.status_color(test.status) + ']')
back = lambda mult: '\b' * mult
out = lambda *a: (sys.stdout.write(' '.join(str(s) for s in a)), sys.stdout.flush())
duplicate_errors = set()
for i, test in enumerate(tests):
headline = '[{}/{}] {} ['.format(i + 1, len(tests), test.name)
print term.bold(headline.ljust(79, '-')),
out(back(8) + ' ' + step_fmt('build'))
try:
build = test.build(args.project)
except Exception:
test.build_failed = True
print
traceback.print_exc()
out(back(7) + step_fmt(' run '))
if not test.build_failed:
try:
success = test.run()
except Exception:
test.ran = True
success = test.success = False
print
traceback.print_exc()
out(back(max(7, len(test.status) + 3)) + ' ' + status_fmt(test))
print
if test.output:
if test.build_failed:
if test.output in duplicate_errors:
continue
else:
duplicate_errors.add(test.output)
for line in test.output.split('\n'):
ERROR = term.bold(term.red('ERROR:'))
WARNING = term.bold(term.yellow('WARNING:'))
if line.startswith('ERROR'):
line = line.replace('ERROR:', ERROR, 1)
elif line.startswith('WARNING'):
line = line.replace('WARNING:', WARNING, 1)
if test.build_failed:
line = line.replace('error:', ERROR)
line = line.replace('warning:', WARNING)
print '> {}'.format(line)
passed = sum(t.success for t in tests if t.ran)
total = sum(t.ran for t in tests)
results = '{} / {} passed, {} skipped '.format(passed, total, len(tests) - total)
if total > 0:
pc = passed / float(total) * 100
percent = '{:.2f}%'.format(pc)
if passed == total:
percent = term.green('100%')
elif pc < 75:
percent = term.red(percent)
else:
percent = term.yellow(percent)
print term.bold((results + '[{}]').format(percent).rjust(80 + len(term.green(''))))
print
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Build and run tests.')
parser.add_argument('--project', help='project directory', default='.')
parser.add_argument('--base', help='test directories to search', required=True)
parser.add_argument('tests', help='test names to run (all by default)', nargs='*')
args = parser.parse_args()
run(args)