mirror of https://github.com/ceph/go-ceph
182 lines
4.5 KiB
Python
182 lines
4.5 KiB
Python
|
#!/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()
|