From 4b5c7923f6e38405f9404bfc2b8147b89ae94432 Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Wed, 29 May 2024 09:28:49 +0100 Subject: [PATCH] etc: add etc/code-coverage-test-*.py --- etc/code-coverage-test-c.py | 151 ++++++++++++++++++++++++++++++++++ etc/code-coverage-test-gap.py | 107 ++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100755 etc/code-coverage-test-c.py create mode 100755 etc/code-coverage-test-gap.py diff --git a/etc/code-coverage-test-c.py b/etc/code-coverage-test-c.py new file mode 100755 index 000000000..62cd03294 --- /dev/null +++ b/etc/code-coverage-test-c.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +""" +""" + +# pylint: disable=invalid-name, broad-except + +import argparse +import tempfile +import subprocess +import sys +import os +import webbrowser + +from os.path import exists, isfile + +_ERR_PREFIX = '\033[31merror: ' + +def exec_string(string): + 'execute the string in a subprocess.' + try: + subprocess.check_call(string, shell=True) + except KeyboardInterrupt: + sys.exit('\033[31m\nKilled!\033[0m') + except (subprocess.CalledProcessError, OSError): + sys.exit(_ERR_PREFIX + 'executing:\n' + string + '\n failed!\033[0m') + +_PARSER = argparse.ArgumentParser(prog='code-coverage-test-c.py', + usage='%(prog)s [options]') +_PARSER.add_argument('files', nargs='+', type=str, + help='the test files you want to check code coverage for' + + '(must be at least one)') +_PARSER.add_argument('--gap-root', nargs='?', type=str, + help='the gap root directory (default: ~/gap)', + default='~/gap/') +_PARSER.add_argument('--pkg', nargs='?', type=str, + help='the package to profile (default: None)', + default=None) +_PARSER.add_argument('--build', dest='build', action='store_true', + help='rebuild GAP (default: False)') +_PARSER.set_defaults(build=False) +_PARSER.add_argument('--open', nargs='?', type=str, + help=('open the lcov html page for this file ' + + '(default: None)'), + default=None) +_PARSER.add_argument('--line', nargs='?', type=str, + help=('open the html page for the file specified by --open' + + ' at this line (default: None)'), + default=None) +_ARGS = _PARSER.parse_args() + +if not _ARGS.gap_root[-1] == '/': + _ARGS.gap_root += '/' + +_ARGS.gap_root = os.path.expanduser(_ARGS.gap_root) +if _ARGS.pkg != None: + _ARGS.pkg = _ARGS.gap_root + '/pkg/' + _ARGS.pkg + +if not (os.path.exists(_ARGS.gap_root) and os.path.isdir(_ARGS.gap_root)): + sys.exit('\033[31mcode-coverage-test-c.py: error: can\'t find gap root' + + ' directory!\033[0m') +if (_ARGS.pkg != None and not (os.path.exists(_ARGS.pkg) and + os.path.isdir(_ARGS.pkg))): + sys.exit('\033[31mcode-coverage-test-c.py: error: can\'t find the pkg' + + ' directory %s\033[0m' % _ARGS.pkg) +for f in _ARGS.files: + if not (os.path.exists(f) and os.path.isfile(f)): + sys.exit('\033[31mcode-coverage-test-c.py: error: ' + f + + ' does not exist!\033[0m') + +_DIR = tempfile.mkdtemp() +print('\033[35musing temporary directory: ' + _DIR + '\033[0m') + +_COMMANDS = 'echo "' +for f in _ARGS.files: + _COMMANDS += 'Test(\\"' + f + '\\");;' +_COMMANDS += '"' + +# TODO build if files changed since last build or built with the wrong flags, +# by looking in config.log + +# for source in : +# if time.ctime(os.path.getmtime(file)) + +if _ARGS.build: + cwd = os.getcwd() + os.chdir(_ARGS.gap_root) + exec_string('''rm -rf bin/ && \ + make clean && \ + ./configure CFLAGS="-O0 -g --coverage" \ + CXXFLAGS="-O0 -g --coverage" \ + LDFLAGS="-O0 -g --coverage" && \ + make -j8''') + if _ARGS.pkg != None: + os.chdir(_ARGS.pkg) + exec_string('rm -rf bin/ && \ + make clean && \ + ./configure CFLAGS="-O0 -g --coverage" \ + CXXFLAGS="-O0 -g --coverage" \ + LDFLAGS="-O0 -g --coverage" && \ + make -j8''') + os.chdir(cwd) + +pro1 = subprocess.Popen(_COMMANDS, stdout=subprocess.PIPE, shell=True) +_RUN_GAP = _ARGS.gap_root + 'bin/gap.sh -A -m 1g -T' + +try: + pro2 = subprocess.Popen(_RUN_GAP, + stdin=pro1.stdout, + shell=True) + pro2.wait() + print('') +except KeyboardInterrupt: + pro1.terminate() + pro1.wait() + pro2.terminate() + pro2.wait() + print('\033[31mKilled!\033[0m') + sys.exit(1) +except Exception: + sys.exit('\033[31mcode-coverage-test-c.py: error: something went wrong ' + + 'calling GAP!\033[0m''') +if _ARGS.pkg != None: + exec_string('lcov --capture --directory ' + _ARGS.pkg + + '/src --output-file ' + _DIR + '/lcov.info') +else: + exec_string('lcov --capture --directory ' + _ARGS.gap_root + + '/src --output-file ' + _DIR + '/lcov.info') + +exec_string('genhtml ' + _DIR + '/lcov.info --output-directory ' + _DIR + + '/lcov-out') + +filename = _DIR + '/lcov-out/' +if _ARGS.open: + filename += _ARGS.open + '.gcov.html' +else: + filename += '/index.html' + +if exists(filename) and isfile(filename): + if _ARGS.open and _ARGS.line: + filename += '#' + _ARGS.line + print('file://' + filename) + try: + webbrowser.get('chrome').open('file://' + filename, new=2) + except Exception: + webbrowser.open('file://' + filename, new=2) +else: + sys.exit('\n' + _ERR_PREFIX + 'Failed to open file://' + filename + + '\033[0m') + +print('\n\033[32mSUCCESS!\033[0m') +sys.exit(0) diff --git a/etc/code-coverage-test-gap.py b/etc/code-coverage-test-gap.py new file mode 100755 index 000000000..e50ea5a89 --- /dev/null +++ b/etc/code-coverage-test-gap.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +This is a simple script to run code coverage for some test files. +""" +# pylint: disable=invalid-name + +import argparse +import os +import re +import subprocess +import sys +import tempfile + +from os.path import exists, isdir, isfile +from os import getcwd + +_ERR_PREFIX = "\033[31mcode-coverage-test-gap.py: error: " +_INFO_PREFIX = "\033[0m\033[1m" + +_PARSER = argparse.ArgumentParser( + prog="code-coverage-test-gap.py", usage="%(prog)s [options]" +) +_PARSER.add_argument( + "tstfiles", + nargs="+", + type=str, + help="the test files you want to check code coverage for" + + "(must be at least one)", +) +_PARSER.add_argument( + "--gap-root", + nargs="?", + type=str, + help="the gap root directory (default: ~/gap)", + default="~/gap/", +) +_PARSER.add_argument( + "--open", + nargs="?", + type=str, + help=("open the html page for this file (default: None)"), + default=None, +) + +_ARGS = _PARSER.parse_args() +if not _ARGS.gap_root[-1] == "/": + _ARGS.gap_root += "/" + +if exists("gap") and isdir("gap"): + _PROFILE_DIR = "/gap/" +elif exists("lib") and isdir("lib"): + _PROFILE_DIR = "/lib/" +else: + sys.exit(f"{_ERR_PREFIX}no directory gap or lib to profile!\033[0m") + +_ARGS.gap_root = os.path.expanduser(_ARGS.gap_root) +if not (exists(_ARGS.gap_root) and isdir(_ARGS.gap_root)): + sys.exit(f"{_ERR_PREFIX}can't find GAP root directory!\033[0m") + +for f in _ARGS.tstfiles: + if not (exists(f) and isfile(f)): + sys.exit(f"{_ERR_PREFIX}{f} does not exist!\033[0m") + +_DIR = tempfile.mkdtemp() +print(f"{_INFO_PREFIX}Using temporary directory: {_DIR}\033[0m") + +_COMMANDS = 'echo "' +_COMMANDS += "".join(rf"Test(\"{f}\");;\n" for f in _ARGS.tstfiles) +_COMMANDS += rf"""UncoverageLineByLine();; +LoadPackage(\"profiling\", false);; +filesdir := \"{getcwd()}{_PROFILE_DIR}\";;\n""" + +_COMMANDS += rf"outdir := \"{_DIR}\";;\n" +_COMMANDS += rf"x := ReadLineByLineProfile(\"{_DIR}/profile.gz\");;\n" +_COMMANDS += 'OutputAnnotatedCodeCoverageFiles(x, filesdir, outdir);"' + +_RUN_GAP = f"{_ARGS.gap_root}/gap -A -m 1g -T --cover {_DIR}/profile.gz" + +with subprocess.Popen(_COMMANDS, stdout=subprocess.PIPE, shell=True) as pro1: + try: + with subprocess.Popen(_RUN_GAP, stdin=pro1.stdout, shell=True) as pro2: + pro2.wait() + except KeyboardInterrupt: + pro1.terminate() + pro1.wait() + sys.exit("\033[31mKilled!\033[0m") + except (subprocess.CalledProcessError, IOError, OSError): + sys.exit(_ERR_PREFIX + "Something went wrong calling GAP!\033[0m") + + +def rewrite_fname(fname: str) -> str: + return fname.replace("/", "_") + + +suffix = "" +if _ARGS.open: + filename = f"{_DIR}/{rewrite_fname(getcwd())}/{rewrite_fname(_ARGS.open)}.html" + p = re.compile(r"") + with open(filename, "r", encoding="utf-8") as f: + m = p.search(f.read()) + if m: + suffix += "#line" + m.group(1) +else: + filename = _DIR + "/index.html" +print(f"{_INFO_PREFIX}\nSUCCESS!\033[0m") +print(f"{_INFO_PREFIX} See {filename}") +sys.exit(0)