summaryrefslogtreecommitdiff
path: root/.config/qutebrowser/scripts/dev/check_coverage.py
diff options
context:
space:
mode:
authorVito Graffagnino <vito@graffagnino.xyz>2020-09-08 18:10:49 +0100
committerVito Graffagnino <vito@graffagnino.xyz>2020-09-08 18:10:49 +0100
commit3b0142cedcde39e4c2097ecd916a870a3ced5ec6 (patch)
tree2116c49a845dfc0945778f2aa3e2118d72be428b /.config/qutebrowser/scripts/dev/check_coverage.py
parent8cc927e930d5b6aafe3e9862a61e81705479a1b4 (diff)
Added the relevent parts of the .config directory. Alss add ssh config
Diffstat (limited to '.config/qutebrowser/scripts/dev/check_coverage.py')
-rw-r--r--.config/qutebrowser/scripts/dev/check_coverage.py348
1 files changed, 348 insertions, 0 deletions
diff --git a/.config/qutebrowser/scripts/dev/check_coverage.py b/.config/qutebrowser/scripts/dev/check_coverage.py
new file mode 100644
index 0000000..32c5afc
--- /dev/null
+++ b/.config/qutebrowser/scripts/dev/check_coverage.py
@@ -0,0 +1,348 @@
+#!/usr/bin/env python3
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+"""Enforce perfect coverage on some files."""
+
+import os
+import os.path
+import sys
+import enum
+import subprocess
+from xml.etree import ElementTree
+
+import attr
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
+ os.pardir))
+
+from scripts import utils as scriptutils
+from qutebrowser.utils import utils
+
+
+@attr.s
+class Message:
+
+ """A message shown by coverage.py."""
+
+ typ = attr.ib()
+ filename = attr.ib()
+ text = attr.ib()
+
+
+MsgType = enum.Enum('MsgType', 'insufficent_coverage, perfect_file')
+
+
+# A list of (test_file, tested_file) tuples. test_file can be None.
+PERFECT_FILES = [
+ (None,
+ 'commands/cmdexc.py'),
+ ('tests/unit/commands/test_cmdutils.py',
+ 'commands/cmdutils.py'),
+ ('tests/unit/commands/test_argparser.py',
+ 'commands/argparser.py'),
+
+ ('tests/unit/browser/webkit/test_cache.py',
+ 'browser/webkit/cache.py'),
+ ('tests/unit/browser/webkit/test_cookies.py',
+ 'browser/webkit/cookies.py'),
+ ('tests/unit/browser/test_history.py',
+ 'browser/history.py'),
+ ('tests/unit/browser/webkit/http/test_http.py',
+ 'browser/webkit/http.py'),
+ ('tests/unit/browser/webkit/http/test_content_disposition.py',
+ 'browser/webkit/rfc6266.py'),
+ # ('tests/unit/browser/webkit/test_webkitelem.py',
+ # 'browser/webkit/webkitelem.py'),
+ # ('tests/unit/browser/webkit/test_webkitelem.py',
+ # 'browser/webelem.py'),
+ ('tests/unit/browser/webkit/network/test_filescheme.py',
+ 'browser/webkit/network/filescheme.py'),
+ ('tests/unit/browser/webkit/network/test_networkreply.py',
+ 'browser/webkit/network/networkreply.py'),
+
+ ('tests/unit/browser/test_signalfilter.py',
+ 'browser/signalfilter.py'),
+ (None,
+ 'browser/webengine/certificateerror.py'),
+ # ('tests/unit/browser/test_tab.py',
+ # 'browser/tab.py'),
+
+ ('tests/unit/keyinput/test_basekeyparser.py',
+ 'keyinput/basekeyparser.py'),
+ ('tests/unit/keyinput/test_keyutils.py',
+ 'keyinput/keyutils.py'),
+
+ ('tests/unit/misc/test_autoupdate.py',
+ 'misc/autoupdate.py'),
+ ('tests/unit/misc/test_readline.py',
+ 'misc/readline.py'),
+ ('tests/unit/misc/test_split.py',
+ 'misc/split.py'),
+ ('tests/unit/misc/test_msgbox.py',
+ 'misc/msgbox.py'),
+ ('tests/unit/misc/test_checkpyver.py',
+ 'misc/checkpyver.py'),
+ ('tests/unit/misc/test_guiprocess.py',
+ 'misc/guiprocess.py'),
+ ('tests/unit/misc/test_editor.py',
+ 'misc/editor.py'),
+ ('tests/unit/misc/test_cmdhistory.py',
+ 'misc/cmdhistory.py'),
+ ('tests/unit/misc/test_ipc.py',
+ 'misc/ipc.py'),
+ ('tests/unit/misc/test_keyhints.py',
+ 'misc/keyhintwidget.py'),
+ ('tests/unit/misc/test_pastebin.py',
+ 'misc/pastebin.py'),
+ (None,
+ 'misc/objects.py'),
+
+ (None,
+ 'mainwindow/statusbar/keystring.py'),
+ ('tests/unit/mainwindow/statusbar/test_percentage.py',
+ 'mainwindow/statusbar/percentage.py'),
+ ('tests/unit/mainwindow/statusbar/test_progress.py',
+ 'mainwindow/statusbar/progress.py'),
+ ('tests/unit/mainwindow/statusbar/test_tabindex.py',
+ 'mainwindow/statusbar/tabindex.py'),
+ ('tests/unit/mainwindow/statusbar/test_textbase.py',
+ 'mainwindow/statusbar/textbase.py'),
+ ('tests/unit/mainwindow/statusbar/test_url.py',
+ 'mainwindow/statusbar/url.py'),
+ ('tests/unit/mainwindow/statusbar/test_backforward.py',
+ 'mainwindow/statusbar/backforward.py'),
+ ('tests/unit/mainwindow/test_messageview.py',
+ 'mainwindow/messageview.py'),
+
+ ('tests/unit/config/test_config.py',
+ 'config/config.py'),
+ ('tests/unit/config/test_configdata.py',
+ 'config/configdata.py'),
+ ('tests/unit/config/test_configexc.py',
+ 'config/configexc.py'),
+ ('tests/unit/config/test_configfiles.py',
+ 'config/configfiles.py'),
+ ('tests/unit/config/test_configtypes.py',
+ 'config/configtypes.py'),
+ ('tests/unit/config/test_configinit.py',
+ 'config/configinit.py'),
+ ('tests/unit/config/test_configcommands.py',
+ 'config/configcommands.py'),
+ ('tests/unit/config/test_configutils.py',
+ 'config/configutils.py'),
+
+ ('tests/unit/utils/test_qtutils.py',
+ 'utils/qtutils.py'),
+ ('tests/unit/utils/test_standarddir.py',
+ 'utils/standarddir.py'),
+ ('tests/unit/utils/test_urlutils.py',
+ 'utils/urlutils.py'),
+ ('tests/unit/utils/usertypes',
+ 'utils/usertypes.py'),
+ ('tests/unit/utils/test_utils.py',
+ 'utils/utils.py'),
+ ('tests/unit/utils/test_version.py',
+ 'utils/version.py'),
+ ('tests/unit/utils/test_debug.py',
+ 'utils/debug.py'),
+ ('tests/unit/utils/test_jinja.py',
+ 'utils/jinja.py'),
+ ('tests/unit/utils/test_error.py',
+ 'utils/error.py'),
+ ('tests/unit/utils/test_javascript.py',
+ 'utils/javascript.py'),
+ ('tests/unit/utils/test_urlmatch.py',
+ 'utils/urlmatch.py'),
+
+ (None,
+ 'completion/models/util.py'),
+ ('tests/unit/completion/test_models.py',
+ 'completion/models/urlmodel.py'),
+ ('tests/unit/completion/test_models.py',
+ 'completion/models/configmodel.py'),
+ ('tests/unit/completion/test_histcategory.py',
+ 'completion/models/histcategory.py'),
+ ('tests/unit/completion/test_listcategory.py',
+ 'completion/models/listcategory.py'),
+
+ ('tests/unit/browser/webengine/test_spell.py',
+ 'browser/webengine/spell.py'),
+
+]
+
+
+# 100% coverage because of end2end tests, but no perfect unit tests yet.
+WHITELISTED_FILES = [
+ 'browser/webkit/webkitinspector.py',
+ 'keyinput/macros.py',
+ 'browser/webkit/webkitelem.py',
+]
+
+
+class Skipped(Exception):
+
+ """Exception raised when skipping coverage checks."""
+
+ def __init__(self, reason):
+ self.reason = reason
+ super().__init__("Skipping coverage checks " + reason)
+
+
+def _get_filename(filename):
+ """Transform the absolute test filenames to relative ones."""
+ if os.path.isabs(filename):
+ basedir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..'))
+ common_path = os.path.commonprefix([basedir, filename])
+ if common_path:
+ filename = filename[len(common_path):].lstrip('/')
+ if filename.startswith('qutebrowser/'):
+ filename = filename.split('/', maxsplit=1)[1]
+
+ return filename
+
+
+def check(fileobj, perfect_files):
+ """Main entry point which parses/checks coverage.xml if applicable."""
+ if not utils.is_linux:
+ raise Skipped("on non-Linux system.")
+ elif '-k' in sys.argv[1:]:
+ raise Skipped("because -k is given.")
+ elif '-m' in sys.argv[1:]:
+ raise Skipped("because -m is given.")
+ elif '--lf' in sys.argv[1:]:
+ raise Skipped("because --lf is given.")
+
+ perfect_src_files = [e[1] for e in perfect_files]
+
+ filename_args = [arg for arg in sys.argv[1:]
+ if arg.startswith('tests' + os.sep)]
+ filtered_files = [tpl[1] for tpl in perfect_files if tpl[0] in
+ filename_args]
+
+ if filename_args and not filtered_files:
+ raise Skipped("because there is nothing to check.")
+
+ tree = ElementTree.parse(fileobj)
+ classes = tree.getroot().findall('./packages/package/classes/class')
+
+ messages = []
+
+ for klass in classes:
+ filename = _get_filename(klass.attrib['filename'])
+
+ line_cov = float(klass.attrib['line-rate']) * 100
+ branch_cov = float(klass.attrib['branch-rate']) * 100
+
+ if filtered_files and filename not in filtered_files:
+ continue
+
+ assert 0 <= line_cov <= 100, line_cov
+ assert 0 <= branch_cov <= 100, branch_cov
+ assert '\\' not in filename, filename
+
+ is_bad = line_cov < 100 or branch_cov < 100
+
+ if filename in perfect_src_files and is_bad:
+ text = "{} has {:.2f}% line and {:.2f}% branch coverage!".format(
+ filename, line_cov, branch_cov)
+ messages.append(Message(MsgType.insufficent_coverage, filename,
+ text))
+ elif (filename not in perfect_src_files and not is_bad and
+ filename not in WHITELISTED_FILES):
+ text = ("{} has 100% coverage but is not in "
+ "perfect_files!".format(filename))
+ messages.append(Message(MsgType.perfect_file, filename, text))
+
+ return messages
+
+
+def main_check():
+ """Check coverage after a test run."""
+ try:
+ with open('coverage.xml', encoding='utf-8') as f:
+ messages = check(f, PERFECT_FILES)
+ except Skipped as e:
+ print(e)
+ messages = []
+
+ if messages:
+ print()
+ print()
+ scriptutils.print_title("Coverage check failed")
+ for msg in messages:
+ print(msg.text)
+ print()
+ filters = ','.join('qutebrowser/' + msg.filename for msg in messages)
+ subprocess.run([sys.executable, '-m', 'coverage', 'report',
+ '--show-missing', '--include', filters], check=True)
+ print()
+ print("To debug this, run 'tox -e py36-pyqt59-cov' "
+ "(or py35-pyqt59-cov) locally and check htmlcov/index.html")
+ print("or check https://codecov.io/github/qutebrowser/qutebrowser")
+ print()
+
+ if 'CI' in os.environ:
+ print("Keeping coverage.xml on CI.")
+ else:
+ os.remove('coverage.xml')
+ return 1 if messages else 0
+
+
+def main_check_all():
+ """Check the coverage for all files individually.
+
+ This makes sure the files have 100% coverage without running unrelated
+ tests.
+
+ This runs pytest with the used executable, so check_coverage.py should be
+ called with something like ./.tox/py36/bin/python.
+ """
+ for test_file, src_file in PERFECT_FILES:
+ if test_file is None:
+ continue
+ subprocess.run(
+ [sys.executable, '-m', 'pytest', '--cov', 'qutebrowser',
+ '--cov-report', 'xml', test_file], check=True)
+ with open('coverage.xml', encoding='utf-8') as f:
+ messages = check(f, [(test_file, src_file)])
+ os.remove('coverage.xml')
+
+ messages = [msg for msg in messages
+ if msg.typ == MsgType.insufficent_coverage]
+ if messages:
+ for msg in messages:
+ print(msg.text)
+ return 1
+ else:
+ print("Check ok!")
+ return 0
+
+
+def main():
+ scriptutils.change_cwd()
+ if '--check-all' in sys.argv:
+ return main_check_all()
+ else:
+ return main_check()
+
+
+if __name__ == '__main__':
+ sys.exit(main())