diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..136e4c555 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Jinja2==2.7.3 +MarkupSafe==0.23 +Pygments==2.0.2 +pyPEG2==2.15.1 +PyYAML==3.11 +colorama==0.3.3 +colorlog==2.6.0 diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py new file mode 100644 index 000000000..4e0c725fd --- /dev/null +++ b/scripts/link_pyqt.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014-2015 Florian Bruhin (The Compiler) + +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Symlink PyQt into a given virtualenv.""" + +import os +import argparse +import shutil +import os.path +import sys +import glob +import subprocess + + +class Error(Exception): + + """Exception raised when linking fails.""" + + pass + + +def verbose_copy(src, dst, *, follow_symlinks=True): + """Copy function for shutil.copytree which prints copied files.""" + print('{} -> {}'.format(src, dst)) + shutil.copy(src, dst, follow_symlinks=follow_symlinks) + + +def get_ignored_files(directory, files): + """Get the files which should be ignored for link_pyqt() on Windows.""" + needed_exts = ('.py', '.dll', '.pyd', '.so') + ignored_dirs = ('examples', 'qml', 'uic', 'doc') + filtered = [] + for f in files: + ext = os.path.splitext(f)[1] + full_path = os.path.join(directory, f) + if os.path.isdir(full_path) and f in ignored_dirs: + filtered.append(f) + elif (ext not in needed_exts) and os.path.isfile(full_path): + filtered.append(f) + return filtered + + +def link_pyqt(sys_path, venv_path): + """Symlink the systemwide PyQt/sip into the venv. + + Args: + sys_path: The path to the system-wide site-packages. + venv_path: The path to the virtualenv site-packages. + """ + globbed_sip = (glob.glob(os.path.join(sys_path, 'sip*.so')) + + glob.glob(os.path.join(sys_path, 'sip*.pyd'))) + if not globbed_sip: + raise Error("Did not find sip in {}!".format(sys_path)) + + files = ['PyQt5'] + files += [os.path.basename(e) for e in globbed_sip] + for fn in files: + source = os.path.join(sys_path, fn) + dest = os.path.join(venv_path, fn) + if not os.path.exists(source): + raise FileNotFoundError(source) + if os.path.exists(dest): + if os.path.isdir(dest) and not os.path.islink(dest): + shutil.rmtree(dest) + else: + os.unlink(dest) + if os.name == 'nt': + if os.path.isdir(source): + shutil.copytree(source, dest, ignore=get_ignored_files, + copy_function=verbose_copy) + else: + print('{} -> {}'.format(source, dest)) + shutil.copy(source, dest) + else: + print('{} -> {}'.format(source, dest)) + os.symlink(source, dest) + + +def get_python_lib(executable): + """Get the python site-packages directory for the given executable.""" + return subprocess.check_output( + [executable, '-c', 'from distutils.sysconfig import get_python_lib\n' + 'print(get_python_lib())'], universal_newlines=True).rstrip() + + +def get_tox_syspython(venv_path): + """Get the system python based on a virtualenv created by tox.""" + path = os.path.join(venv_path, '.tox-config1') + with open(path, encoding='ascii') as f: + line = f.readline() + _md5, sys_python = line.rstrip().split(' ') + return sys_python + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser() + parser.add_argument('path', help="Base path to the venv.") + parser.add_argument('site_path', help="Path to the venv site-packages " + "folder (for tox).", nargs='?') + parser.add_argument('--tox', help="Add when called via tox.", + action='store_true') + args = parser.parse_args() + + if args.tox: + sys_path = get_python_lib(get_tox_syspython(args.path)) + else: + sys_path = get_python_lib(sys.executable) + + if args.site_path is None: + subdir = 'Scripts' if os.name == 'nt' else 'bin' + executable = os.path.join(args.path, subdir, 'python') + venv_path = get_python_lib(executable) + else: + venv_path = args.site_path + + link_pyqt(sys_path, venv_path) + + +if __name__ == '__main__': + try: + main() + except Error as e: + print(str(e), file=sys.stderr) + sys.exit(1) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py new file mode 100644 index 000000000..019e61128 --- /dev/null +++ b/scripts/misc_checks.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014-2015 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Various small code checkers.""" + +import os +import sys +import os.path +import argparse +import subprocess +import tokenize +import traceback + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) + +from scripts import utils + + +def check_git(): + """Check for uncommited git files..""" + if not os.path.isdir(".git"): + print("No .git dir, ignoring") + print() + return False + untracked = [] + changed = [] + gitst = subprocess.check_output(['git', 'status', '--porcelain']) + gitst = gitst.decode('UTF-8').strip() + for line in gitst.splitlines(): + s, name = line.split(maxsplit=1) + if s == '??' and name != '.venv/': + untracked.append(name) + elif s == 'M': + changed.append(name) + status = True + if untracked: + status = False + utils.print_col("Untracked files:", 'red') + print('\n'.join(untracked)) + if changed: + status = False + utils.print_col("Uncommited changes:", 'red') + print('\n'.join(changed)) + print() + return status + + +def check_vcs_conflict(target): + """Check VCS conflict markers.""" + try: + ok = True + for (dirpath, _dirnames, filenames) in os.walk(target): + for name in (e for e in filenames if e.endswith('.py')): + fn = os.path.join(dirpath, name) + with tokenize.open(fn) as f: + for line in f: + if any(line.startswith(c * 7) for c in '<>=|'): + print("Found conflict marker in {}".format(fn)) + ok = False + print() + return ok + except Exception: + traceback.print_exc() + return None + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser() + parser.add_argument('checker', choices=('git', 'vcs'), + help="Which checker to run.") + parser.add_argument('target', help="What to check", nargs='*') + args = parser.parse_args() + if args.checker == 'git': + ok = check_git() + return 0 if ok else 1 + elif args.checker == 'vcs': + is_ok = True + for target in args.target: + ok = check_vcs_conflict(target) + if not ok: + is_ok = False + return 0 if is_ok else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/print_coverage.py b/scripts/print_coverage.py new file mode 100644 index 000000000..4e9443ee9 --- /dev/null +++ b/scripts/print_coverage.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014-2015 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Print the unittest coverage based on the HTML output. + +This can probably be deleted when switching to py.test +""" + + +import os.path +import html.parser + + +class Parser(html.parser.HTMLParser): + + """HTML parser to get the percentage from coverage's HTML output.""" + + def __init__(self): + super().__init__(convert_charrefs=True) + self._active = False + self.percentage = None + + def handle_starttag(self, tag, attrs): + if tag == 'span' and dict(attrs).get('class', None) == 'pc_cov': + self._active = True + + def handle_endtag(self, _tag): + self._active = False + + def handle_data(self, data): + if self._active: + self.percentage = data + + +def main(): + """Main entry point.""" + p = Parser() + with open(os.path.join('htmlcov', 'index.html'), encoding='utf-8') as f: + p.feed(f.read()) + print('COVERAGE: {}'.format(p.percentage)) + + +if __name__ == '__main__': + main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..2ada7d997 --- /dev/null +++ b/tox.ini @@ -0,0 +1,78 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = unittests,misc,pep257,flake8,pylint,pyroma,check-manifest + +[testenv] +basepython = python3 + +[testenv:mkvenv] +commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envsitepackagesdir} +envdir = {toxinidir}/.venv +usedevelop = true + +[testenv:unittests] +deps = + coverage==3.7.1 +commands = + {[testenv:mkvenv]commands} + {envpython} -m coverage erase + {envpython} -m coverage run --branch --source=qutebrowser -m unittest + {envpython} -m coverage html + {envpython} scripts/print_coverage.py + +[testenv:misc] +commands = + {envpython} scripts/misc_checks.py git + {envpython} scripts/misc_checks.py vcs qutebrowser scripts + +[testenv:pylint] +skip_install = true +setenv = PYTHONPATH={toxinidir}/scripts:{env:PYTHONPATH:} +deps = + -rrequirements.txt + astroid==1.3.6 + beautifulsoup4==4.3.2 + pylint==1.4.3 + logilab-common==0.63.2 + six==1.9.0 +commands = + {[testenv:mkvenv]commands} + {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no + +[testenv:pep257] +skip_install = true +deps = pep257==0.5.0 +# Disabled checks: +# D102: Docstring missing, will be handled by others +# D209: Blank line before closing """ (removed from PEP257) +# D402: First line should not be function's signature (false-positives) +commands = {envdir}/bin/pep257 scripts qutebrowser --ignore=D102,D209,D402 '--match=(?!resources|test_content_disposition).*\.py' + +[testenv:flake8] +deps = + -rrequirements.txt + pyflakes==0.8.1 + pep8==1.5.7 # rq.filter: <=1.5.7 + flake8==2.4.0 +commands = + {[testenv:mkvenv]commands} + {envdir}/bin/flake8 scripts qutebrowser --config=.flake8 + +[testenv:pyroma] +deps = + pyroma==1.7 + docutils==0.12 +commands = + {[testenv:mkvenv]commands} + {envdir}/bin/pyroma . + +[testenv:check-manifest] +deps = + check-manifest==0.23 +commands = + {[testenv:mkvenv]commands} + {envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__'