Blame tools/coverage.py

Packit 4afa83
#!/usr/bin/env python3
Packit 4afa83
Packit 4afa83
import os, sys, glob, pickle, subprocess
Packit 4afa83
Packit 4afa83
sys.path.insert(0, os.path.dirname(__file__))
Packit 4afa83
from clang import cindex
Packit 4afa83
sys.path = sys.path[1:]
Packit 4afa83
Packit 4afa83
def configure_libclang():
Packit 4afa83
    llvm_libdirs = ['/usr/lib/llvm-3.2/lib', '/usr/lib64/llvm']
Packit 4afa83
Packit 4afa83
    try:
Packit 4afa83
        libdir = subprocess.check_output(['llvm-config', '--libdir']).decode('utf-8').strip()
Packit 4afa83
        llvm_libdirs.insert(0, libdir)
Packit 4afa83
    except OSError:
Packit 4afa83
        pass
Packit 4afa83
Packit 4afa83
    for d in llvm_libdirs:
Packit 4afa83
        if not os.path.exists(d):
Packit 4afa83
            continue
Packit 4afa83
Packit 4afa83
        files = glob.glob(os.path.join(d, 'libclang.so*'))
Packit 4afa83
Packit 4afa83
        if len(files) != 0:
Packit 4afa83
            cindex.Config.set_library_file(files[0])
Packit 4afa83
            return
Packit 4afa83
Packit 4afa83
class Call:
Packit 4afa83
    def __init__(self, cursor, decl):
Packit 4afa83
        self.ident = cursor.displayname.decode('utf-8')
Packit 4afa83
        self.filename = cursor.location.file.name.decode('utf-8')
Packit 4afa83
Packit 4afa83
        ex = cursor.extent
Packit 4afa83
Packit 4afa83
        self.start_line = ex.start.line
Packit 4afa83
        self.start_column = ex.start.column
Packit 4afa83
Packit 4afa83
        self.end_line = ex.end.line
Packit 4afa83
        self.end_column = ex.end.column
Packit 4afa83
Packit 4afa83
        self.decl_filename = decl.location.file.name.decode('utf-8')
Packit 4afa83
Packit 4afa83
class Definition:
Packit 4afa83
    def __init__(self, cursor):
Packit 4afa83
        self.ident = cursor.spelling.decode('utf-8')
Packit 4afa83
        self.display = cursor.displayname.decode('utf-8')
Packit 4afa83
Packit 4afa83
        self.filename = cursor.location.file.name.decode('utf-8')
Packit 4afa83
Packit 4afa83
        ex = cursor.extent
Packit 4afa83
Packit 4afa83
        self.start_line = ex.start.line
Packit 4afa83
        self.start_column = ex.start.column
Packit 4afa83
Packit 4afa83
        self.end_line = ex.end.line
Packit 4afa83
        self.end_column = ex.end.column
Packit 4afa83
Packit 4afa83
def process_diagnostics(tu):
Packit 4afa83
    diagnostics = tu.diagnostics
Packit 4afa83
Packit 4afa83
    haserr = False
Packit 4afa83
Packit 4afa83
    for d in diagnostics:
Packit 4afa83
        sys.stderr.write('{0}\n'.format(d.format.decode('utf-8')))
Packit 4afa83
Packit 4afa83
        if d.severity > cindex.Diagnostic.Warning:
Packit 4afa83
            haserr = True
Packit 4afa83
Packit 4afa83
    if haserr:
Packit 4afa83
        sys.exit(1)
Packit 4afa83
Packit 4afa83
def walk_cursors(tu, files):
Packit 4afa83
    proc = list(tu.cursor.get_children())
Packit 4afa83
Packit 4afa83
    while len(proc) > 0:
Packit 4afa83
        cursor = proc[0]
Packit 4afa83
        proc = proc[1:]
Packit 4afa83
Packit 4afa83
        if cursor.location.file is None:
Packit 4afa83
            continue
Packit 4afa83
Packit 4afa83
        fname = cursor.location.file.name.decode('utf-8')
Packit 4afa83
Packit 4afa83
        if fname in files:
Packit 4afa83
            yield cursor
Packit 4afa83
Packit 4afa83
            proc += list(cursor.get_children())
Packit 4afa83
Packit 4afa83
def newer(a, b):
Packit 4afa83
    try:
Packit 4afa83
        return os.stat(a).st_mtime > os.stat(b).st_mtime
Packit 4afa83
    except:
Packit 4afa83
        return True
Packit 4afa83
Packit 4afa83
def scan_libgit2_glib(cflags, files, git2dir):
Packit 4afa83
    files = [os.path.abspath(f) for f in files]
Packit 4afa83
Packit 4afa83
    dname = os.path.dirname(__file__)
Packit 4afa83
    allcalls = {}
Packit 4afa83
Packit 4afa83
    l = 0
Packit 4afa83
Packit 4afa83
    if not os.getenv('SILENT'):
Packit 4afa83
        sys.stderr.write('\n')
Packit 4afa83
Packit 4afa83
    i = 0
Packit 4afa83
Packit 4afa83
    for f in files:
Packit 4afa83
        if not os.getenv('SILENT'):
Packit 4afa83
            name = os.path.basename(f)
Packit 4afa83
Packit 4afa83
            if len(name) > l:
Packit 4afa83
                l = len(name)
Packit 4afa83
Packit 4afa83
            perc = int((i / len(files)) * 100)
Packit 4afa83
Packit 4afa83
            sys.stderr.write('[{0: >3}%] Processing ... {1}{2}\r'.format(perc, name, ' ' * (l - len(name))))
Packit 4afa83
Packit 4afa83
        i += 1
Packit 4afa83
Packit 4afa83
        astf = os.path.join(dname, '.' + os.path.basename(f) + '.cache')
Packit 4afa83
Packit 4afa83
        if not newer(f, astf):
Packit 4afa83
            with open(astf, 'rb') as fo:
Packit 4afa83
                calls = pickle.load(fo)
Packit 4afa83
        else:
Packit 4afa83
            tu = cindex.TranslationUnit.from_source(f, cflags)
Packit 4afa83
Packit 4afa83
            process_diagnostics(tu)
Packit 4afa83
            calls = {}
Packit 4afa83
Packit 4afa83
            for cursor in walk_cursors(tu, files):
Packit 4afa83
                if cursor.kind == cindex.CursorKind.CALL_EXPR or \
Packit 4afa83
                   cursor.kind == cindex.CursorKind.DECL_REF_EXPR:
Packit 4afa83
Packit 4afa83
                    cdecl = cursor.get_referenced()
Packit 4afa83
Packit 4afa83
                    if cdecl.kind != cindex.CursorKind.FUNCTION_DECL:
Packit 4afa83
                        continue
Packit 4afa83
Packit 4afa83
                    if (not cdecl is None) and (not cdecl.location.file is None):
Packit 4afa83
                        fdefname = cdecl.location.file.name.decode('utf-8')
Packit 4afa83
Packit 4afa83
                        if fdefname.startswith(git2dir):
Packit 4afa83
                            call = Call(cursor, cdecl)
Packit 4afa83
Packit 4afa83
                            if call.ident in calls:
Packit 4afa83
                                calls[call.ident].append(call)
Packit 4afa83
                            else:
Packit 4afa83
                                calls[call.ident] = [call]
Packit 4afa83
Packit 4afa83
        with open(astf, 'wb') as fo:
Packit 4afa83
            pickle.dump(calls, fo)
Packit 4afa83
Packit 4afa83
        for k in calls:
Packit 4afa83
            if k in allcalls:
Packit 4afa83
                allcalls[k] += calls[k]
Packit 4afa83
            else:
Packit 4afa83
                allcalls[k] = list(calls[k])
Packit 4afa83
Packit 4afa83
    if not os.getenv('SILENT'):
Packit 4afa83
        sys.stderr.write('\r[100%] Processing ... done{0}\n'.format(' ' * (l - 4)))
Packit 4afa83
Packit 4afa83
    return allcalls
Packit 4afa83
Packit 4afa83
def scan_libgit2(cflags, git2dir):
Packit 4afa83
    tu = cindex.TranslationUnit.from_source(git2dir + '.h', cflags)
Packit 4afa83
    process_diagnostics(tu)
Packit 4afa83
    headers = glob.glob(os.path.join(git2dir, '*.h'))
Packit 4afa83
Packit 4afa83
    defs = {}
Packit 4afa83
Packit 4afa83
    objapi = ['lookup', 'lookup_prefix', 'free', 'id', 'owner']
Packit 4afa83
    objderiv = ['commit', 'tree', 'tag', 'blob']
Packit 4afa83
Packit 4afa83
    ignore = set()
Packit 4afa83
Packit 4afa83
    for deriv in objderiv:
Packit 4afa83
        for api in objapi:
Packit 4afa83
            ignore.add('git_' + deriv + '_' + api)
Packit 4afa83
Packit 4afa83
    for cursor in walk_cursors(tu, headers):
Packit 4afa83
        if cursor.kind == cindex.CursorKind.FUNCTION_DECL:
Packit 4afa83
            deff = Definition(cursor)
Packit 4afa83
Packit 4afa83
            if not deff.ident in ignore:
Packit 4afa83
                defs[deff.ident] = deff
Packit 4afa83
Packit 4afa83
    return defs
Packit 4afa83
Packit 4afa83
configure_libclang()
Packit 4afa83
Packit 4afa83
pos = sys.argv.index('--')
Packit 4afa83
Packit 4afa83
cflags = sys.argv[1:pos]
Packit 4afa83
files = sys.argv[pos+1:]
Packit 4afa83
Packit 4afa83
incdir = os.getenv('LIBGIT2_INCLUDE_DIR')
Packit 4afa83
Packit 4afa83
defs = scan_libgit2(cflags, incdir)
Packit 4afa83
calls = scan_libgit2_glib(cflags, files, incdir)
Packit 4afa83
Packit 4afa83
notused = {}
Packit 4afa83
perfile = {}
Packit 4afa83
nperfile = {}
Packit 4afa83
Packit 4afa83
for d in defs:
Packit 4afa83
    o = defs[d]
Packit 4afa83
Packit 4afa83
    if not d in calls:
Packit 4afa83
        notused[d] = defs[d]
Packit 4afa83
Packit 4afa83
        if not o.filename in nperfile:
Packit 4afa83
            nperfile[o.filename] = [o]
Packit 4afa83
        else:
Packit 4afa83
            nperfile[o.filename].append(o)
Packit 4afa83
Packit 4afa83
    if not o.filename in perfile:
Packit 4afa83
        perfile[o.filename] = [o]
Packit 4afa83
    else:
Packit 4afa83
        perfile[o.filename].append(o)
Packit 4afa83
Packit 4afa83
ss = [notused[f] for f in notused]
Packit 4afa83
ss.sort(key=lambda x: '{0} {1}'.format(os.path.basename(x.filename), x.ident))
Packit 4afa83
Packit 4afa83
lastf = None
Packit 4afa83
Packit 4afa83
keys = list(perfile.keys())
Packit 4afa83
keys.sort()
Packit 4afa83
Packit 4afa83
for filename in keys:
Packit 4afa83
    b = os.path.basename(filename)
Packit 4afa83
    f = perfile[filename]
Packit 4afa83
Packit 4afa83
    n_perfile = len(f)
Packit 4afa83
Packit 4afa83
    if filename in nperfile:
Packit 4afa83
        n_nperfile = len(nperfile[filename])
Packit 4afa83
    else:
Packit 4afa83
        n_nperfile = 0
Packit 4afa83
Packit 4afa83
    perc = int(((n_perfile - n_nperfile) / n_perfile) * 100)
Packit 4afa83
Packit 4afa83
    print('\n  File {0}, coverage {1}% ({2} out of {3}):'.format(b, perc, n_perfile - n_nperfile, n_perfile))
Packit 4afa83
Packit 4afa83
    cp = list(f)
Packit 4afa83
    cp.sort(key=lambda x: "{0} {1}".format(not x.ident in calls, x.ident))
Packit 4afa83
Packit 4afa83
    for d in cp:
Packit 4afa83
        if d.ident in calls:
Packit 4afa83
            print('      \033[32m✓ {0}\033[0m'.format(d.display))
Packit 4afa83
        else:
Packit 4afa83
            print('      \033[31m✗ {0}\033[0m'.format(d.display))
Packit 4afa83
Packit 4afa83
perc = int(((len(defs) - len(notused)) / len(defs)) * 100)
Packit 4afa83
Packit 4afa83
print('\nTotal coverage: {0}% ({1} functions out of {2} are being called)\n'.format(perc, len(defs) - len(notused), len(defs)))
Packit 4afa83
Packit 4afa83
# vi:ts=4:et