mirror of https://github.com/ceph/go-ceph
contrib: create a hacky script for comparing api coverage
This fairly hacky script can be used to check how much of the various apis are covered by the go-ceph library. It doesn't care about a lot of subtlety at the moment but does give a fair idea of what types of functions are implemented vs. those that are not. Requires the tool 'castxml'. Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
parent
280b9b01d4
commit
62c53cd682
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
USAGE = """
|
||||
Compare go-ceph bindings coverage of the ceph api to available C
|
||||
code.
|
||||
Note that this script is not comprehensive and does not claim to be.
|
||||
It only takes functions into account and does not care about overlap
|
||||
in functionality (function x is a superset of y and thus y does
|
||||
not need to be implemented) or deprecated functions.
|
||||
|
||||
Example: python3 apicompare.py --check=rbd --list .
|
||||
Example: python3 apicompare.py -crbd -crados -ccephfs .
|
||||
"""
|
||||
|
||||
|
||||
RBD_C = """
|
||||
#include <rbd/librbd.h>
|
||||
#include <rbd/features.h>
|
||||
|
||||
#define GNDN
|
||||
GNDN int foo(int x) {
|
||||
return x;
|
||||
}
|
||||
"""
|
||||
|
||||
RADOS_C = """
|
||||
#include <rados/librados.h>
|
||||
|
||||
#define GNDN
|
||||
GNDN int foo(int x) {
|
||||
return x;
|
||||
}
|
||||
"""
|
||||
|
||||
CEPHFS_C = """
|
||||
#define FILE_OFFSET_BITS 64
|
||||
#include <stdlib.h>
|
||||
#define __USE_FILE_OFFSET64
|
||||
#include <cephfs/libcephfs.h>
|
||||
|
||||
#define GNDN
|
||||
GNDN int foo(int x) {
|
||||
return x;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class Results(object):
|
||||
def __init__(self):
|
||||
self.total = 0
|
||||
self.referenced = []
|
||||
self.documented = []
|
||||
self.missing = []
|
||||
|
||||
|
||||
def go_sources(srcdir):
|
||||
pat = '/'.join((srcdir, '*.go'))
|
||||
for fn in glob.glob(pat):
|
||||
if fn.endswith("_test.go"):
|
||||
continue
|
||||
yield fn
|
||||
|
||||
|
||||
def check_go(path, funcs):
|
||||
gotext = []
|
||||
for fn in go_sources(path):
|
||||
with open(fn) as fh:
|
||||
gotext.append(fh.read())
|
||||
gotext = '\n'.join(gotext)
|
||||
res = Results()
|
||||
res.total = len(funcs)
|
||||
for fname in funcs:
|
||||
regex = re.compile('\\bC\\.{}\\b'.format(fname))
|
||||
m = regex.findall(gotext)
|
||||
if m:
|
||||
res.referenced.append(fname)
|
||||
else:
|
||||
res.missing.append(fname)
|
||||
return res
|
||||
|
||||
|
||||
def api_funcs(code_stub, prefix, dump_xml=False):
|
||||
funcs = []
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
tmpc = os.path.join(tempdir, 'temp.c')
|
||||
tmpxml = os.path.join(tempdir, 'temp_c.xml')
|
||||
with open(tmpc, 'w') as fh:
|
||||
fh.write(code_stub)
|
||||
subprocess.check_call(
|
||||
['castxml', '--castxml-output=1', '-o', tmpxml, tmpc])
|
||||
if dump_xml:
|
||||
with open(tmpxml) as fh:
|
||||
print(fh.read())
|
||||
x = xml.etree.ElementTree.parse(tmpxml)
|
||||
for fe in x.getroot().findall('Function'):
|
||||
name = fe.attrib.get('name', '')
|
||||
if not name.startswith(prefix):
|
||||
continue
|
||||
funcs.append(name)
|
||||
return funcs
|
||||
|
||||
|
||||
def check_for_castxml():
|
||||
try:
|
||||
with open(os.devnull) as nullfh:
|
||||
subprocess.check_call(
|
||||
['castxml'],
|
||||
stdin=nullfh,
|
||||
stderr=nullfh,
|
||||
stdout=nullfh)
|
||||
except subprocess.CalledProcessError:
|
||||
sys.stderr.write('error: "castxml" binary must be installed\n')
|
||||
sys.exit(2)
|
||||
except FileNotFoundError:
|
||||
sys.stderr.write('error: "castxml" binary must be installed\n')
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def report(label, res, list_functions=False):
|
||||
print("{} functions covered: {}/{} : {:.2f}%".format(
|
||||
label,
|
||||
len(res.referenced),
|
||||
res.total,
|
||||
(100.0 * len(res.referenced)) / res.total))
|
||||
print("{} functions remain: {}/{} : {:.2f}%".format(
|
||||
label,
|
||||
len(res.missing),
|
||||
res.total,
|
||||
(100.0 * len(res.missing)) / res.total))
|
||||
|
||||
if list_functions:
|
||||
for n in sorted(res.referenced):
|
||||
print(" Covered: {}".format(n))
|
||||
for n in sorted(res.missing):
|
||||
print(" Missing: {}".format(n))
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description='compare api coverage extent',
|
||||
usage=USAGE)
|
||||
ap.add_argument(
|
||||
'--check', '-c',
|
||||
choices=['rbd', 'rados', 'cephfs'],
|
||||
action='append')
|
||||
ap.add_argument(
|
||||
'--list-functions',
|
||||
action='store_true')
|
||||
ap.add_argument('GO_SRC')
|
||||
cli = ap.parse_args()
|
||||
|
||||
check_for_castxml()
|
||||
os.chdir(cli.GO_SRC)
|
||||
|
||||
if 'rbd' in (cli.check or []):
|
||||
funcs = api_funcs(RBD_C, 'rbd_')
|
||||
res = check_go('rbd', funcs)
|
||||
report('RBD', res, cli.list_functions)
|
||||
if 'rados' in (cli.check or []):
|
||||
funcs = api_funcs(RADOS_C, 'rados_')
|
||||
res = check_go('rados', funcs)
|
||||
report('RADOS', res, cli.list_functions)
|
||||
if 'cephfs' in (cli.check or []):
|
||||
funcs = api_funcs(CEPHFS_C, 'ceph_')
|
||||
res = check_go('cephfs', funcs)
|
||||
report('CephFS', res, cli.list_functions)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue