# -*- coding: utf-8 -*- """ Flask Extension Tests ~~~~~~~~~~~~~~~~~~~~~ Tests the Flask extensions. :copyright: (c) 2015 by Ali Afshar. :license: BSD, see LICENSE for more details. """ import os import sys import shutil import urllib2 import tempfile import subprocess import argparse from flask import json from setuptools.package_index import PackageIndex from setuptools.archive_util import unpack_archive flask_svc_url = 'http://flask.pocoo.org/extensions/' # OS X has awful paths when using mkstemp or gettempdir(). I don't # care about security or clashes here, so pick something that is # actually memorable. if sys.platform == 'darwin': _tempdir = '/private/tmp' else: _tempdir = tempfile.gettempdir() tdir = _tempdir + '/flaskext-test' flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # virtualenv hack *cough* os.environ['PYTHONDONTWRITEBYTECODE'] = '' RESULT_TEMPATE = u'''\ Flask-Extension Test Results

Flask-Extension Test Results

This page contains the detailed test results for the test run of all {{ 'approved' if approved }} Flask extensions.

Summary

{%- for result in results %} {% set outcome = 'success' if result.success else 'failed' %} {%- endfor %}
Extension Version Author License Outcome {%- for iptr, _ in results[0].logs|dictsort %} {{ iptr }} {%- endfor %}
{{ result.name }} {{ result.version }} {{ result.author }} {{ result.license }} {{ outcome }} {%- for iptr, _ in result.logs|dictsort %} see log {%- endfor %}

Test Logs

Detailed test logs for all tests on all platforms: {%- for result in results %} {%- for iptr, log in result.logs|dictsort %}

{{ result.name }} - {{ result.version }} [{{ iptr }}]

{{ log }}
{%- endfor %} {%- endfor %} ''' def log(msg, *args): print('[EXTTEST] ' + (msg % args)) class TestResult(object): def __init__(self, name, folder, statuscode, interpreters): intrptr = os.path.join(folder, '.tox/%s/bin/python' % interpreters[0]) self.statuscode = statuscode self.folder = folder self.success = statuscode == 0 def fetch(field): try: c = subprocess.Popen([intrptr, 'setup.py', '--' + field], cwd=folder, stdout=subprocess.PIPE) return c.communicate()[0].strip() except OSError: return '?' self.name = name self.license = fetch('license') self.author = fetch('author') self.version = fetch('version') self.logs = {} for interpreter in interpreters: logfile = os.path.join(folder, '.tox/%s/log/test.log' % interpreter) if os.path.isfile(logfile): self.logs[interpreter] = open(logfile).read() else: self.logs[interpreter] = '' def create_tdir(): try: shutil.rmtree(tdir) except Exception: pass os.mkdir(tdir) def package_flask(): distfolder = tdir + '/.flask-dist' c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar', '--dist', distfolder], cwd=flaskdir) c.wait() return os.path.join(distfolder, os.listdir(distfolder)[0]) def get_test_command(checkout_dir): if os.path.isfile(checkout_dir + '/Makefile'): return 'make test' return 'python setup.py test' def fetch_extensions_list(): req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'}) d = urllib2.urlopen(req).read() data = json.loads(d) for ext in data['extensions']: yield ext def checkout_extension(name): log('Downloading extension %s to temporary folder', name) root = os.path.join(tdir, name) os.mkdir(root) checkout_path = PackageIndex().download(name, root) unpack_archive(checkout_path, root) path = None for fn in os.listdir(root): path = os.path.join(root, fn) if os.path.isdir(path): break log('Downloaded to %s', path) return path tox_template = """[tox] envlist=%(env)s [testenv] deps= %(deps)s distribute py commands=bash flaskext-runtest.sh {envlogdir}/test.log downloadcache=%(cache)s """ def create_tox_ini(checkout_path, interpreters, flask_dep): tox_path = os.path.join(checkout_path, 'tox-flask-test.ini') if not os.path.exists(tox_path): with open(tox_path, 'w') as f: f.write(tox_template % { 'env': ','.join(interpreters), 'cache': tdir, 'deps': flask_dep }) return tox_path def iter_extensions(only_approved=True): for ext in fetch_extensions_list(): if ext['approved'] or not only_approved: yield ext['name'] def test_extension(name, interpreters, flask_dep): checkout_path = checkout_extension(name) log('Running tests with tox in %s', checkout_path) # figure out the test command and write a wrapper script. We # can't write that directly into the tox ini because tox does # not invoke the command from the shell so we have no chance # to pipe the output into a logfile. The /dev/null hack is # to trick py.test (if used) into not guessing widths from the # invoking terminal. test_command = get_test_command(checkout_path) log('Test command: %s', test_command) f = open(checkout_path + '/flaskext-runtest.sh', 'w') f.write(test_command + ' &> "$1" < /dev/null\n') f.close() # if there is a tox.ini, remove it, it will cause troubles # for us. Remove it if present, we are running tox ourselves # afterall. create_tox_ini(checkout_path, interpreters, flask_dep) rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path) return TestResult(name, checkout_path, rv, interpreters) def run_tests(extensions, interpreters): results = {} create_tdir() log('Packaging Flask') flask_dep = package_flask() log('Running extension tests') log('Temporary Environment: %s', tdir) for name in extensions: log('Testing %s', name) result = test_extension(name, interpreters, flask_dep) if result.success: log('Extension test succeeded') else: log('Extension test failed') results[name] = result return results def render_results(results, approved): from jinja2 import Template items = results.values() items.sort(key=lambda x: x.name.lower()) rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items, approved=approved) fd, filename = tempfile.mkstemp(suffix='.html') os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n') return filename def main(): parser = argparse.ArgumentParser(description='Runs Flask extension tests') parser.add_argument('--all', dest='all', action='store_true', help='run against all extensions, not just approved') parser.add_argument('--browse', dest='browse', action='store_true', help='show browser with the result summary') parser.add_argument('--env', dest='env', default='py25,py26,py27', help='the tox environments to run against') parser.add_argument('--extension=', dest='extension', default=None, help='tests a single extension') args = parser.parse_args() if args.extension is not None: only_approved = False extensions = [args.extension] else: only_approved = not args.all extensions = iter_extensions(only_approved) results = run_tests(extensions, [x.strip() for x in args.env.split(',')]) filename = render_results(results, only_approved) if args.browse: import webbrowser webbrowser.open('file:///' + filename.lstrip('/')) print('Results written to {}'.format(filename)) if __name__ == '__main__': main()