From 700c0fec9105f7ba1418526ef7640f83233f2777 Mon Sep 17 00:00:00 2001 From: Xavier MARCELET Date: Mon, 6 Mar 2017 08:59:49 +0100 Subject: [PATCH 1/2] * strengthen and sanitize code * replaces --json option by --format * add unittests * add proper CI pipeline --- .dir-locals.el | 10 + .travis.yml | 11 +- Makefile | 104 ++ README.md | 54 +- coverxygen/__init__.py | 273 ++++-- coverxygen/__main__.py | 77 +- coverxygen/test/__init__.py | 6 + coverxygen/test/data/invalid.xml | 667 +++++++++++++ coverxygen/test/data/real.xml | 1463 ++++++++++++++++++++++++++++ coverxygen/test/data/valid.xml | 668 +++++++++++++ coverxygen/test/test_coverxygen.py | 292 ++++++ debian/changelog | 16 +- devtools/coverage.sh | 38 + devtools/statuses.py | 303 ++++++ devtools/unittests.py | 184 ++++ devtools/xtdlint.py | 116 +++ requirements.dev.txt | 6 + 17 files changed, 4145 insertions(+), 143 deletions(-) create mode 100644 .dir-locals.el create mode 100644 Makefile create mode 100644 coverxygen/test/__init__.py create mode 100644 coverxygen/test/data/invalid.xml create mode 100644 coverxygen/test/data/real.xml create mode 100644 coverxygen/test/data/valid.xml create mode 100644 coverxygen/test/test_coverxygen.py create mode 100755 devtools/coverage.sh create mode 100755 devtools/statuses.py create mode 100755 devtools/unittests.py create mode 100755 devtools/xtdlint.py create mode 100644 requirements.dev.txt diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..dd55314 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,10 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((nil . ((eval . (setq xtdmacs-code-python-test-bin-path + (concat + (file-name-directory + (let ((d (dir-locals-find-file "."))) + (if (stringp d) d (car d)))) + "devtools/unittests.py" + )))))) diff --git a/.travis.yml b/.travis.yml index 432067d..8bf44f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,20 @@ dist: trusty cache: - apt - + - pip install: - - sudo apt-get install graphviz debhelper python-all python-setuptools python3-all python3-setuptools lintian + - sudo apt-get install graphviz debhelper python-all python-setuptools python3-all python3-setuptools python3-pip lintian script: - export PATH=/usr/bin:${PATH} + - sudo -H python3 -m pip install --upgrade pip + - sudo -H python3 -m pip install -rrequirements.dev.txt --upgrade + - python3 ./devtools/statuses.py --commit=${TRAVIS_COMMIT} --build-id=${TRAVIS_BUILD_ID} --token=${token} --pull-id=${TRAVIS_PULL_REQUEST} + - ./devtools/coverage.sh python3 - dpkg-buildpackage -uc -us - lintian ../python-coverxygen_*.deb - lintian ../python3-coverxygen_*.deb + +after_success: + - coveralls diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..68f4daa --- /dev/null +++ b/Makefile @@ -0,0 +1,104 @@ +INPUT_SRC=$(shell find . -name '*.py' | grep -v test_) +INPUT_TEST=$(shell find . -name 'test_*.py') +PKG_NAME=coverxygen +PKG_VERSION=$(shell python3 -c 'import $(PKG_NAME); print($(PKG_NAME).__version__);') + +all: dev + +# dev tty + +dev: dev-doc dev-check dev-pylint dev-coverage + +dev-doc: +# @echo "---------------" +# @echo " Documentation " +# @echo "---------------" +# @make -C docs vhtml +# @echo "" + +dev-check: $(SOURCES) + @echo "-----------" + @echo " Unittests " + @echo "-----------" + @./devtools/unittests.py -v + @echo "" + +dev-pylint: + @echo "--------" + @echo " PyLint " + @echo "--------" + @./devtools/xtdlint.py --rcfile=.pylintrc --reports=no -j4 coverxygen -f parseable || true + @echo "" + +dev-coverage: + @echo "--------" + @echo " Coverage " + @echo "--------" + @./devtools/coverage.sh python3 + @python3 -m coverage report + @echo "" + +# report + +report: report-coverage report-doc report-pylint report-check + +report-coverage: build/coverage/index.html +build/coverage/index.html: $(INPUT_SRC) $(INPUT_TEST) Makefile + @echo "generating coverage report ..." + @mkdir -p $(dir $@) + @./devtools/coverage.sh python3 2> /dev/null + @python3 -m coverage html -d $(dir $@) + @echo "generating coverage report ... done" + +report-pylint: build/pylint/index.html +build/pylint/index.html: $(INPUT_SRC) .pylintrc Makefile + @echo "generating pylint report ..." + @mkdir -p $(dir $@) + @./devtools/xtdlint.py --rcfile=.pylintrc -j4 coverxygen -f html > $@ || true + @echo "generating pylint report ... done" + +report-doc: #build/docs/html/xtd.html +# build/docs/html/xtd.html: $(INPUT_SRC) Makefile docs/conf.py +# @echo "generating documentation ..." +# @mkdir -p $(dir $@) +# @make -C docs -s html > /dev/null +# @echo "generating documentation ... done" + +report-check: build/unittests/index.json +build/unittests/index.json: $(INPUT_SRC) $(INPUT_TEST) Makefile + @echo "generating unittests report ..." + @mkdir -p $(dir $@) + @./devtools/unittests.py --format json -v | json_pp > $@ + @echo "generating unittests report ... done" + +# dist + +dist: dist/$(PGK_NAME)-$(PKG_VERSION).tar.gz +dist/$(PGK_NAME)-$(PKG_VERSION).tar.gz: $(INPUT_SRC) setup.py setup.cfg + @./setup.py sdist + +# show + +show: show-coverage show-doc show-check show-pylint +show-coverage: build/coverage/index.html + @sensible-browser $< & +show-doc: #build/docs/html/xtd.html +# @sensible-browser $< & +show-check: build/unittests/index.json + @sensible-browser $< & +show-pylint: build/pylint/index.html + @sensible-browser $< & + +# clean + +clean: clean-doc clean-coverage clean-check clean-pylint clean-dist +# clean-doc: +# @rm -rf build/docs docs/xtd*.rst +clean-coverage: + @rm -rf build/coverage +clean-check: + @rm -rf build/unittests +clean-pylint: + @rm -rf build/pylint +clean-dist: + @rm -rf dist/ $(PKG_NAME).egg-info diff --git a/README.md b/README.md index 005bf5d..e25cf6e 100644 --- a/README.md +++ b/README.md @@ -67,32 +67,38 @@ python -m coverxygen --xml-path --output doc-coverage. Full usage : ``` -usage: coverxygen [-h] [--version] [--json] --xml-dir XML_DIR --output OUTPUT [--prefix PREFIX] [--scope SCOPE] [--kind KIND] +usage: coverxygen [-h] [--version] [--verbose] [--json JSON] [--format FORMAT] --xml-dir XML_DIR --output OUTPUT --root-dir ROOT_DIR [--prefix PREFIX] [--scope SCOPE] [--kind KIND] optional arguments: - -h, --help show this help message and exit - --version prints version - --json output raw data as json file format - --xml-dir XML_DIR path to generated doxygen XML directory - --output OUTPUT destination output file (- for stdout) - --prefix PREFIX keep only file matching given prefix (default /) - --scope SCOPE comma-separated list of items' scope to include : - - public : public member elements - - protected : protected member elements - - private : private member elements - - all : all above - --kind KIND comma-separated list of items' type to include : - - enum : enum definitions - - typedef : typedef definitions - - variable : variable definitions - - function : function definitions - - class : class definitions - - struct : struct definitions - - define : define definitions - - file : file definitions - - namespace : namespace definitions - - page : page definitions - - all : all above + -h, --help show this help message and exit + --version prints version + --verbose enabled verbose output + --json JSON (deprecated) same as --format json-legacy + --format FORMAT output file format : + lcov : lcov compatible format (default) + json-legacy : legacy json format + lcov : simpler json format + --xml-dir XML_DIR path to generated doxygen XML directory + --output OUTPUT destination output file (- for stdout) + --root-dir ROOT_DIR root source directory used to match prefix forrelative path generated files + --prefix PREFIX keep only file matching given path prefix + --scope SCOPE comma-separated list of items' scope to include : + - public : public member elements + - protected : protected member elements + - private : private member elements + - all : all above + --kind KIND comma-separated list of items' type to include : + - enum : enum definitions + - typedef : typedef definitions + - variable : variable definitions + - function : function definitions + - class : class definitions + - struct : struct definitions + - define : define definitions + - file : file definitions + - namespace : namespace definitions + - page : page definitions + - all : all above ``` ## Run lcov or genhtml diff --git a/coverxygen/__init__.py b/coverxygen/__init__.py index 5912562..b74b212 100644 --- a/coverxygen/__init__.py +++ b/coverxygen/__init__.py @@ -10,7 +10,7 @@ __author__ = "Xavier MARCELET " __copyright__ = "Copyright (C) 2016 Xavier MARCELET" -__version__ = "1.2.0" +__version__ = "1.3.0" __description__ = "Generate doxygen's documentation coverage report" __url__ = "https://github.com/psycofdj/coverxygen" __download_url__ = "https://github.com/psycofdj/coverxygen/tarball/%s" % __version__ @@ -28,134 +28,205 @@ #------------------------------------------------------------------------------ class Coverxygen(object): - def __init__(self, p_path, p_output, p_scope, p_kind, p_prefix, p_json, p_srcDir, p_verbose): - self.m_path = p_path + def __init__(self, p_path, p_output, p_scope, p_kind, p_prefix, p_format, p_rootDir, p_verbose): + self.m_root = p_path self.m_output = p_output self.m_scope = p_scope self.m_kind = p_kind self.m_prefix = p_prefix - self.m_json = p_json - self.m_srcDir = p_srcDir + self.m_format = p_format + self.m_rootDir = p_rootDir self.m_verbose = p_verbose - + @staticmethod def error(p_format, *p_args): - l_message = p_format % p_args - sys.stderr.write("error: %s" % l_message) - sys.exit(1) + try: + l_msg = p_format % p_args + except TypeError: + l_msg = "invalid message format '%s' with args '%s'" % (p_format, str(p_args)) + raise RuntimeError(l_msg) def verbose(self, p_fmt, *p_args): if self.m_verbose: l_msg = p_fmt % p_args sys.stderr.write(l_msg + "\n") + @staticmethod + def get_index_path_from_root(p_root): + l_index = os.path.join(p_root, "index.xml") + if not os.path.exists(l_index): + Coverxygen.error("could not find root index.xml file %s", l_index) + return l_index - def process_item(self, p_item, p_path, p_result): - l_file = p_path - l_lineNo = 1 - l_name = "" - l_scope = p_item.get('prot') - l_kind = p_item.get('kind') - - if l_scope and (not l_scope in self.m_scope): - return - if not l_kind in self.m_kind: - return + @staticmethod + def get_file_path_from_root(p_root, p_name): + l_fileName = "%s.xml" % p_name + l_filePath = os.path.join(p_root, l_fileName) + if not os.path.exists(l_filePath): + Coverxygen.error("could not find indexed file %s", l_filePath) + return l_filePath + @staticmethod + def get_xmldoc_from_file(p_file): + try: + l_doc = ET.parse(p_file) + except BaseException as l_error: + Coverxygen.error("error while parsing xml file %s : %s", p_file, str(l_error)) + return l_doc - l_documented = False - for c_key in ('briefdescription', 'detaileddescription', 'inbodydescription'): - if p_item.findall("./%s/" % (c_key)): - l_documented = True - break + @staticmethod + def extract_name(p_node): + l_id = p_node.get("id") + l_def = p_node.find("./definition") + l_nam = p_node.find("./name") + if l_def is not None: + return l_def.text + if l_nam is not None: + return l_nam.text + elif l_id is not None : + return l_id + Coverxygen.error("unable to deduce name from node %s", ET.tostring(p_node)) - l_def = p_item.find('./definition') - l_nam = p_item.find('./name') - l_loc = p_item.find('./location') + @staticmethod + def extract_documented(p_node): + for c_key in ["briefdescription", "detaileddescription", "inbodydescription"]: + l_node = p_node.find("./%s" % c_key) + if l_node is not None: + l_content = "".join(l_node.itertext()).strip() + if len(l_content): + return True + return False + @staticmethod + def extract_location(p_node, p_file, p_rootDir): + l_file = p_file + l_line = 1 + l_loc = p_node.find('./location') if l_loc is not None: - l_file = l_loc.get('file') - l_lineNo = l_loc.get('line') - if l_lineNo is None: - return - l_lineNo = int(l_lineNo) - - self.verbose("processing item of type %s at %s:%d", l_kind, l_file, l_lineNo) + l_file = l_loc.get("file") + l_line = l_loc.get("line", 1) + if (l_line is None) or (l_file is None): + Coverxygen.error("unable to extract location from file %s, node : %s", + p_file, ET.tostring(p_node)) + l_line = int(l_line) + l_file = Coverxygen.get_absolute_path(l_file, p_rootDir) + return l_file, l_line - if not l_file.startswith("/"): - l_file = os.path.join(self.m_srcDir, l_file) - - if not l_file.startswith(self.m_prefix): - return + @staticmethod + def get_absolute_path(p_file, p_rootDir): + l_path = p_file + if not p_file.startswith("/"): + l_path = os.path.join(p_rootDir, p_file) + return os.path.abspath(l_path) + + def should_filter_out(self, p_node, p_file, p_line): + l_scope = p_node.get('prot') + l_kind = p_node.get('kind') + if (not l_scope in self.m_scope) or (not l_kind in self.m_kind): + return True + if not p_file.startswith(self.m_prefix): + return True + self.verbose("found symbol of type %s at %s:%d", l_kind, p_file, p_line) + return False + + def process_symbol(self, p_node, p_filePath): + l_name = self.extract_name(p_node) + l_isDocumented = self.extract_documented(p_node) + l_file, l_line = self.extract_location(p_node, p_filePath, self.m_rootDir) + if self.should_filter_out(p_node, l_file, l_line): + return {} + return { + "symbol" : l_name, + "documented" : l_isDocumented, + "line" : l_line, + "file" : l_file + } - if l_def is not None: - l_name = l_def.text - elif l_nam is not None: - l_name = l_nam.text + @staticmethod + def merge_symbols(p_results, p_symbols): + for c_symbol in p_symbols: + if not len(c_symbol): + continue + l_file = c_symbol["file"] + if not l_file in p_results: + p_results[l_file] = [] + p_results[l_file].append(c_symbol) + + def process_file(self, p_filePath, p_results): + self.verbose("processing file : %s", p_filePath) + l_symbols = [] + l_xmlDoc = self.get_xmldoc_from_file(p_filePath) + l_xmlNodes = l_xmlDoc.findall("./compounddef//memberdef") + l_xmlNodes += l_xmlDoc.findall("./compounddef") + for c_def in l_xmlNodes: + l_symbol = self.process_symbol(c_def, p_filePath) + l_symbols.append(l_symbol) + self.merge_symbols(p_results, l_symbols) + + def process_index(self, p_xmlDoc): + l_results = {} + for c_entry in p_xmlDoc.findall('compound'): + l_kind = c_entry.get("kind") + l_refid = c_entry.get("refid") + if not l_kind: + self.error("missing kind attribute on compound element : %s", str(c_entry)) + if not l_kind: + self.error("missing refid attribute on compound element : %s", str(c_entry)) + if l_kind == "dir": + continue + l_filePath = self.get_file_path_from_root(self.m_root, l_refid) + self.process_file(l_filePath, l_results) + return l_results + + def output_results(self, p_results): + l_outStream = self.output_get_stream(self.m_output) + if self.m_format == "json": + self.output_print_json(l_outStream, p_results) + elif self.m_format == "json-legacy": + self.output_print_json_legacy(l_outStream, p_results) + elif self.m_format == "lcov": + self.output_print_lcov(l_outStream, p_results) else: - l_name = p_item.get('id') - - if not l_file in p_result: - p_result[l_file] = [] + self.error("invalid requested output format '%s'", self.m_format) - p_result[l_file].append({ - "symbol" : l_name, - "documented" : l_documented, - "line" : l_lineNo, - "file" : l_file - }) - - def process_file(self, p_path): - self.verbose("processing file : %s", p_path) + def process(self): + l_indexPath = self.get_index_path_from_root(self.m_root) + l_xmlDoc = self.get_xmldoc_from_file(l_indexPath) + l_results = self.process_index(l_xmlDoc) + self.output_results(l_results) - l_defs = {} - l_files = [] + @staticmethod + def output_get_stream(p_output): + if p_output == "-": + return sys.stdout try: - l_tree = ET.parse(p_path) - except ET.ParseError as l_error: - self.error("failed to parse ", p_path, l_error) - - for c_def in l_tree.findall("./compounddef//memberdef"): - self.process_item(c_def, p_path, l_defs) - for c_def in l_tree.findall("./compounddef"): - self.process_item(c_def, p_path, l_defs) - - if len(l_defs): - l_files.append(l_defs) - - return l_files + l_file = open(p_output, "w") + except BaseException as l_error: + Coverxygen.error("unable to write file %s : %s", p_output, str(l_error)) + return l_file - def process(self): - l_index = os.path.join(self.m_path, "index.xml") - if not os.path.exists(l_index): - self.error("could not find root index.xml file %s", l_index) - l_tree = ET.parse(l_index) + @staticmethod + def output_print_json(p_stream, p_results): + p_stream.write(json.dumps(p_results, indent=2)) - if self.m_output == "-": - l_output = sys.stdout - else: - l_output = open(self.m_output, "w") + @staticmethod + def output_print_json_legacy(p_stream, p_results): + l_res = [] + for c_file, c_symbols in p_results.items(): + l_res.append({ c_file : c_symbols }) + p_stream.write(json.dumps(l_res, indent=2)) - l_result = [] - for c_entry in l_tree.findall('compound'): - if c_entry.get('kind') in ['dir']: - continue - l_file = os.path.join (self.m_path, "%s.xml" % (c_entry.get('refid'))) - l_result += self.process_file(l_file) - - if not self.m_json: - for c_data in l_result: - for c_file, c_data in c_data.items(): - l_output.write("SF:%s\n" % c_file) - for c_item in c_data: - l_value = 1 - if not c_item["documented"]: - l_value = -1 - l_output.write("DA:%d,%d\n" % (c_item["line"], l_value)) - l_output.write("end_of_record\n") - else: - l_output.write(json.dumps(l_result, indent=2)) + @staticmethod + def output_print_lcov(p_stream, p_results): + for c_file, c_data in p_results.items(): + p_stream.write("SF:%s\n" % c_file) + for c_item in c_data: + l_value = 1 + if not c_item["documented"]: + l_value = -1 + p_stream.write("DA:%d,%d\n" % (c_item["line"], l_value)) + p_stream.write("end_of_record\n") # Local Variables: -# ispell-local-dictionary: "american" +# ispell-local-dictionary: "en" # End: diff --git a/coverxygen/__main__.py b/coverxygen/__main__.py index ab3dd07..7c8dad0 100755 --- a/coverxygen/__main__.py +++ b/coverxygen/__main__.py @@ -1,11 +1,14 @@ # -*- mode: python; coding: utf-8 -*- #------------------------------------------------------------------------------ +from __future__ import print_function + import sys import argparse -import coverxygen from argparse import RawTextHelpFormatter +import coverxygen + #------------------------------------------------------------------------------ def main(): @@ -14,13 +17,42 @@ def main(): sys.exit(0) l_parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, prog="coverxygen") - l_parser.add_argument("--version", action="store_true", help ="prints version", default=False) - l_parser.add_argument("--verbose", action="store_true", help ="verbose output", default=False) - l_parser.add_argument("--json", action="store_true", help ="output raw data as json file format", default=False) - l_parser.add_argument("--xml-dir", action="store", help ="path to generated doxygen XML directory", required=True) - l_parser.add_argument("--output", action="store", help ="destination output file (- for stdout)", required=True) - l_parser.add_argument("--src-dir", action="store", help ="root source directory", required=True) - l_parser.add_argument("--prefix", action="store", help ="keep only file matching given prefix", default="/") + l_parser.add_argument("--version", + action="store_true", + help ="prints version", + default=False) + l_parser.add_argument("--verbose", + action="store_true", + help ="enabled verbose output", + default=False) + l_parser.add_argument("--json", + action="store_true", + help="(deprecated) same as --format json-legacy", + default=None) + l_parser.add_argument("--format", + action="store", + help="output file format : \n" + "lcov : lcov compatible format (default)\n" + "json-legacy : legacy json format\n" + "lcov : simpler json format\n", + default="lcov") + l_parser.add_argument("--xml-dir", + action="store", + help ="path to generated doxygen XML directory", + required=True) + l_parser.add_argument("--output", + action="store", + help ="destination output file (- for stdout)", + required=True) + l_parser.add_argument("--src-dir", + action="store", + help ="root source directory used to match prefix for" + "relative path generated files", + required=True) + l_parser.add_argument("--prefix", + action="store", + help ="keep only file matching given path prefix", + default="/") l_parser.add_argument("--scope", action="store", help="comma-separated list of items' scope to include : \n" @@ -29,7 +61,6 @@ def main(): " - private : private member elements\n" " - all : all above\n", default="all") - l_parser.add_argument("--kind", action="store", help="comma-separated list of items' type to include : \n" @@ -47,18 +78,34 @@ def main(): default="all") l_result = l_parser.parse_args() - if "all" == l_result.scope: + if l_result.scope == "all": l_result.scope = "public,protected,private" - if "all" == l_result.kind: + if l_result.kind == "all": l_result.kind = "enum,typedef,variable,function,class,struct,define,file,namespace,page" l_result.scope = l_result.scope.split(",") - l_result.kind = l_result.kind.split(",") + l_result.kind = l_result.kind.split(",") if not l_result: - error("couldn't parse parameters") - l_obj = coverxygen.Coverxygen(l_result.xml_dir, l_result.output, l_result.scope, l_result.kind, l_result.prefix, l_result.json, l_result.src_dir, l_result.verbose) - l_obj.process() + sys.stderr.write("error: couldn't parse parameters\n") + sys.exit(1) + + l_format = l_result.format + if l_result.json: + l_format = "json-legacy" + l_obj = coverxygen.Coverxygen(l_result.xml_dir, + l_result.output, + l_result.scope, + l_result.kind, + l_result.prefix, + l_format, + l_result.src_dir, + l_result.verbose) + try: + l_obj.process() + except RuntimeError as l_error: + sys.stderr.write("error: %s\n" % str(l_error)) + sys.exit(1) if __name__ == "__main__": main() diff --git a/coverxygen/test/__init__.py b/coverxygen/test/__init__.py new file mode 100644 index 0000000..02a3ab9 --- /dev/null +++ b/coverxygen/test/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 +#------------------------------------------------------------------# + +__author__ = "Xavier MARCELET " + +#------------------------------------------------------------------# diff --git a/coverxygen/test/data/invalid.xml b/coverxygen/test/data/invalid.xml new file mode 100644 index 0000000..fe45884 --- /dev/null +++ b/coverxygen/test/data/invalid.xml @@ -0,0 +1,667 @@ + + + xtd::log::Appender + t_filter + t_filters + m_formatter + m_filters + Appender + ~Appender + setFormatter + addFilter + clearFilters + clearFormatter + log + print + + xtd::Application + argument + mandatory + none + requirement + optional + mandatory + t_sig_handler + t_callback + t_option_list + ::TestApplication + m_binName + m_logLevel + m_remainingArgs + m_rcsid + m_disableExit + m_disableCatch + m_optionList + m_helpText + m_runThread + m_ioService + m_work + m_signals + m_signalHandlerMap + Application + ~Application + execute + parseConfig + checkOptions + initialize + process + addSignalHandler + getVersion + addOption + isOptionGiven + addHelpMsg + bindDir + bindFile + bindGiven + bindString + bindRegex + bindCallback + bindValueIfGiven + bindNumber + bindValues + bindAccumulator + error_nohelp + error + warn + readArgs + usage + handleSignal + + xtd::tty::attrs + s + reset + bold + dim + underlined + blink + reverse + hidden + unset + reset + dim + bold + underlined + blink + reverse + hidden + m_attrs + attrs + attrs + from_string + to_string + operator| + operator|= + operator== + getValues + isSet + + xtd::log::AutoWidth + m_widths + m_fields + AutoWidth + AutoWidth + operator() + + xtd::mixins::Cloneable + Parent + Cloneable + ~Cloneable + clone + + xtd::mixins::CloneableBase + Parent + clone + + xtd::tty::color + c + unset + black + red + green + yellow + blue + magenta + cyan + gray + normal + lblack + lred + lgreen + lyellow + lblue + lmagenta + lcyan + lgray + white + unset + normal + black + red + green + yellow + blue + magenta + cyan + gray + lblack + lred + lgreen + lyellow + lblue + lmagenta + lcyan + lgray + white + m_value + m_extended + color + color + operator== + isExtended + getValue + isSet + from_string + to_string + + xtd::log::ColoredFormatter + t_styles + t_style_modifier + ConfLoader + ::TestColoredFormatter + m_fieldStyles + m_styleModifiers + ColoredFormatter + ColoredFormatter + ~ColoredFormatter + setAttrs + setFgColor + setBgColor + setStyle + setStyle + setStyles + addStyleModifier + create + createFields + getFulllog + + xtd::log::ConfLoader + t_properties + t_appender_constructor + t_formatter_constructor + ::TestConfLoader + m_appenders + m_formatters + m_appenderConstructors + m_formatterConstructors + configure + registerAppender + registerFormatter + getFormatter + getAppender + createLogger + + xtd::error + m_message + error + ~error + what + + xtd::log::Fields + this_type + m_name + m_module + m_threadid + m_slevel + m_ilevel + m_message + m_filename + m_line + m_function + m_location + m_time + m_fulllog + m_pid + m_ppid + m_functors + Fields + get + exists + get + set + + xtd::format + vargs + vargs + vargs_noexcept + + xtd::log::FormattedRecord + m_hasLoc + m_fulllog + m_message + m_function + m_filename + m_line + FormattedRecord + FormattedRecord + + xtd::log::Formatter + t_formats + t_format_modifier + ConfLoader + ::TestFormatter + m_fmt + m_locFmt + m_timeFmt + m_fieldFormats + m_formatModifiers + Formatter + Formatter + ~Formatter + setLayout + setLayouts + setFieldFormat + setFieldFormats + addFormatModifier + format + create + createFields + getFulllog + resolveLayout + resolveTime + + xtd::log::log_error + + xtd::log::Logger + t_appenders + ::TestLogger + m_module + m_name + m_level + m_appenders + m_mutex + Logger + Logger + Logger + ~Logger + getName + getLevel + setName + setLevel + addAppender + clearAppenders + emerg + alert + crit + err + warning + notice + info + debug + log + getAppenders + + xtd::log::MemoryAppender + ConfLoader + ::TestMemoryAppender + m_maxRecords + m_records + m_mutex + MemoryAppender + ~MemoryAppender + copyRecords + print + create + + xtd::config::parse_error + + xtd::config::Parser + t_refs + t_data + t_result + m_data + m_params + Parser + Parser + Parser + get + get + get + get + get_default + parse + parse + parse + search + translate + resolveExt + resolveRefs + extractKey + + xtd::mixins::PureCloneableBase + Parent + clone + + xtd::log::Record + m_name + m_module + m_level + m_format + m_time + m_threadID + m_pid + m_ppid + Record + + xtd::log::RootLogger + t_loggers + t_levels + ::TestLogger + m_loggers + m_mutex + RootLogger + RootLogger + get + getLevels + initialize + setAllLevels + setAllValueLevels + updateLevels + getLevelTo + setLevelTo + emergTo + alertTo + critTo + errTo + warningTo + infoTo + noticeTo + debugTo + getClosestParent + + xtd::mixins::Singleton + ms_instance + Singleton + Singleton + get + + xtd::log::Stream + m_ref + m_stream + ms_mutex + Stream + Stream + flush + operator<< + + xtd::log::StreamAppender + ConfLoader + ::TestStreamAppender + m_stream + StreamAppender + StreamAppender + ~StreamAppender + create + print + + xtd::tty::style + m_fg + m_bg + m_attrs + style + style + getFgColor + getBgColor + getAttrs + setFgColor + setBgColor + setAttrs + + xtd::log::StyleByLevel + m_conds + StyleByLevel + operator() + + xtd::log::StyleMatch + m_match + m_style + StyleMatch + operator() + + xtd::log::SyslogAppender + ConfLoader + ::TestSyslogAppender + m_opts + m_facility + ms_instances + ms_mutex + SyslogAppender + ~SyslogAppender + to_facility + to_option + create + print + + xtd::Application::t_option + m_shortOpt + m_longOpt + m_description + m_argumentType + m_callback + m_status + m_given + + xtd::tty::Text + m_str + m_style + Text + Text + Text + str + operator string + + xtd::log::Logger::thread_counter + ms_ids + ms_mutex + ms_mainID + get_id + + xtd::text::xml + t_entity + t_data + ms_entityList + encode + encode_copy + initialize + + xtd + status + ok + error + timeout + notfound + next + operator<< + valueof + to_enum + + xtd::config + xtd_error_class + + xtd::config::impl + + xtd::log + level + emerg + alert + crit + err + warning + notice + info + debug + from + from + to_value + to_string + is_valid + is_valid + getRoot + operator<< + operator<< + emerg + alert + crit + err + warning + info + notice + debug + raise + xtd_error_class + + xtd::log::@21 + + xtd::mixins + + xtd::text + + xtd::tty + operator<< + operator<< + operator<< + + config.hh + + core.hh + + exemple1.cc + main + + formatter.cc + main + + Application.cc + rcsid + + Application.hh + + Application.hh + + Grammar.cc + + Grammar.hh + + Parser.cc + + Parser.hh + + types.hh + + types.hh + sptr + fn + + error.hh + xtd_error_class + + format.hh + + log.hh + + log.hh + + Appender.cc + + Appender.hh + + ColoredFormatter.cc + + ColoredFormatter.hh + + ConfLoader.cc + + ConfLoader.hh + XTD_CORE_LOG_REGISTER_APPENDER + XTD_CORE_LOG_REGISTER_FORMATTER + + Fields.hh + + FormatModifiers.cc + + FormatModifiers.hh + + Formatter.cc + + Formatter.hh + + fwd.hh + + helpers.cc + + helpers.hh + + Logger.cc + + Logger.hh + + logtypes.hh + HERE + + MemoryAppender.cc + + MemoryAppender.hh + + Stream.cc + + Stream.hh + + StreamAppender.cc + + StreamAppender.hh + + SyslogAppender.cc + + SyslogAppender.hh + + cloneable.hh + + singleton.hh + + text.cc + + text.hh + + tty.cc + + tty.hh + + tty.hh + + types.cc + + core_config + + core_log + + core_tty + + todo + + /home/psyco/dev/xtdcpp/core/src/config + + /home/psyco/dev/xtdcpp/core + + /home/psyco/dev/xtdcpp/core/doc + + /home/psyco/dev/xtdcpp/core/doc/example + + /home/psyco/dev/xtdcpp/core/src/log + + /home/psyco/dev/xtdcpp/core/doc/example/log + /home/psyco/dev/xtdcpp/core/src/mixins + + /home/psyco/dev/xtdcpp/core/src + + index + + diff --git a/coverxygen/test/data/real.xml b/coverxygen/test/data/real.xml new file mode 100644 index 0000000..11ac78e --- /dev/null +++ b/coverxygen/test/data/real.xml @@ -0,0 +1,1463 @@ + + + + xtd::Application + Application.hh + xtd::Application::t_option + + + argument + + mandatory + = 1 + + mandatory argument for option + + + + + + none + = 2 + +forbidden argument for option + + + + +Behavior switch for the argument of an option. + + + + + + + + requirement + + optional + = 0 + +option is optional on command line + + + + + mandatory + = 1 + +option must be given on command line + + + + +Behavior for the presence of the option itself. + + + + + + + + std::function< void(void)> + typedef std::function<void(void)> xtd::Application::t_sig_handler + + t_sig_handler + +Signal handler functor prototype,. + +addSignalHandler + + + + + + + + + std::function< void(const string &, const t_option &)> + typedef std::function<void(const string&, const t_option&)> xtd::Application::t_callback + + t_callback + + + + + + + + + + vector< t_option > + typedef vector<t_option> xtd::Application::t_option_list + + t_option_list + + + + + + + + + + + + friend class + friend class ::TestApplication + + ::TestApplication + + + + + + + + + + + + string + string xtd::Application::m_binName + + m_binName + +binary name (argv[0]) + + + + + + execute + isOptionGiven + + + uint32_t + uint32_t xtd::Application::m_logLevel + + m_logLevel + +log level read from command line + + + + + + Application + execute + + + vector< string > + vector<string> xtd::Application::m_remainingArgs + + m_remainingArgs + +positional command line arguments + + + + + + addOption + + + string + string xtd::Application::m_rcsid + + m_rcsid + +binary identity information + + + + + + getVersion + + + bool + bool xtd::Application::m_disableExit + + m_disableExit + +holds exit activation state + + + + + + isOptionGiven + + + bool + bool xtd::Application::m_disableCatch + + m_disableCatch + +holds exception catching state + + + + + + execute + + + + + t_option_list + t_option_list xtd::Application::m_optionList + + m_optionList + + + + + + + + + + string + string xtd::Application::m_helpText + + m_helpText + + + + + + + + + + boost::thread + boost::thread xtd::Application::m_runThread + + m_runThread + + + + + + + + + + boost::asio::io_service + boost::asio::io_service xtd::Application::m_ioService + + m_ioService + + + + + + + + + + boost::asio::io_service::work + boost::asio::io_service::work xtd::Application::m_work + + m_work + + + + + + + + + + boost::asio::signal_set + boost::asio::signal_set xtd::Application::m_signals + + m_signals + + + + + + + + + + map< int, t_sig_handler > + map<int, t_sig_handler> xtd::Application::m_signalHandlerMap + + m_signalHandlerMap + + + + + + + + + + + + + xtd::Application::Application + (bool p_disableExit=false, bool p_disableCatch=false) + Application + + bool + p_disableExit + false + + + bool + p_disableCatch + false + + +Constructor. + + + +p_disableExit + + +If true, throws exception instead of exiting on error + + + +p_disableCatch + + +If true, exception are not caught at top level + + + + +std::runtime_error + + + + + + + + + + addOption + none + optional + bindCallback + mandatory + bindValues + m_logLevel + + + + xtd::Application::~Application + (void) + ~Application + + void + + +Destructor. + + + + + + + + int + int xtd::Application::execute + (int p_argc, const char *const p_argv[]) + execute + + int + p_argc + + + const char *const + p_argv + [] + + +main entry point, usually called with main(int,char**)'s arguments + + + +p_argc + + +argument count + + + +p_argv + + +argument list (first is binary name) + + +depends on Application::process implementation, usually 0 if process succeed + + + + + m_binName + error_nohelp + xtd::log::getRoot + xtd::log::RootLogger::initialize + xtd::log::crit + parseConfig + checkOptions + xtd::log::RootLogger::setAllLevels + xtd::log::from + m_logLevel + initialize + process + m_disableCatch + HERE + + + + + void + virtual void xtd::Application::parseConfig + (void) + parseConfig + + void + + +Parse application configuration, (default nothing) + + + + + + execute + + + void + virtual void xtd::Application::checkOptions + (void) + checkOptions + + void + + +Check read options, (default nothing) + + + + + + execute + + + void + virtual void xtd::Application::initialize + (void) + initialize + + void + + +initialize application, (default nothing) + + + + + + process + addSignalHandler + getVersion + addOption + isOptionGiven + addHelpMsg + bindDir + bindFile + bindGiven + bindString + bindRegex + bindCallback + bindValueIfGiven + bindNumber + bindValues + bindAccumulator + error_nohelp + error + warn + execute + + + int + int xtd::Application::process + (void) + process + + void + + +Main application process function, (default nothing) + +Up to the user, generally 0 in case of success + + + + + execute + initialize + + + bool + bool xtd::Application::addSignalHandler + (int p_signalNumber, t_sig_handler p_handler) + addSignalHandler + + int + p_signalNumber + + + t_sig_handler + p_handler + + +register a signal callback + + + +p_signalNumber + + +signal ID (man signal 7) + + + +p_handler + + +handling callback + + +True if signal registered correctly, false if signal was already registered + + + + + initialize + + + const string & + const string & xtd::Application::getVersion + (void) const + getVersion + + void + + +Get RCSID identity informations. + + + + + + m_rcsid + initialize + + + void + void xtd::Application::addOption + (const char p_shortOpt, const string &p_longOpt, const argument p_argType, const requirement p_status, const string &p_description, t_callback p_callback) + addOption + + const char + p_shortOpt + + + const string & + p_longOpt + + + const argument + p_argType + + + const requirement + p_status + + + const string & + p_description + + + t_callback + p_callback + + +Register an option to parser. + + + +p_shortOpt + + +short form, single character + + + +p_longOpt + + +long form, not starting with dash + + + +p_argType + + +shall we expect an argument + + + +p_status + + +should the option be given by user + + + +p_description + + +usage help description + + + +p_callback + + +functor to call when option is encountered + + +Option must : +a unique short forma unique long formshort and long forms must not start by a dash (-) +Programs logs and exit in case of error. + + + + error + none + mandatory + optional + m_remainingArgs + Application + initialize + + + bool + bool xtd::Application::isOptionGiven + (const string &p_optionName) const + isOptionGiven + + const string & + p_optionName + + +Tells if given option was given on command line. + + + +p_optionName + + +long-form or short-form of the option + + +true if option was given + + + + + m_binName + none + mandatory + m_disableExit + initialize + + + void + void xtd::Application::addHelpMsg + (const string &p_helpMessage) + addHelpMsg + + const string & + p_helpMessage + + +Add additional usage line to display on help or errors. + + + +p_helpMessage + + +Message line to add + + + + + + + initialize + + + t_callback + Application::t_callback xtd::Application::bindDir + (string &p_target) const + bindDir + + string & + p_target + + +Bind option's parameter to a directory. + + + +p_target + + +Reference variable to store the option's value + + +generated option callback + + + + + error + initialize + + + t_callback + Application::t_callback xtd::Application::bindFile + (string &p_target, bool p_readable=true) const + bindFile + + string & + p_target + + + bool + p_readable + true + + +Bind option's parameter to a file name. + + + +p_target + + +Reference variable to store the option's value + + + +p_readable + + +If true, check that given file name exists and is readable + + +generated option callback + + + + + error + initialize + + + t_callback + Application::t_callback xtd::Application::bindGiven + (bool &p_target) const + bindGiven + + bool & + p_target + + +Set targeted variable to true if option is given on command line. + + + +p_target + + +Reference variable + + +generated option callback + + + + + initialize + + + t_callback + Application::t_callback xtd::Application::bindString + (string &p_target) const + bindString + + string & + p_target + + +Set targeted variable to option's parameter. + + + +p_target + + +Reference variable + + +generated option callback + + + + + initialize + + + t_callback + Application::t_callback xtd::Application::bindRegex + (string &p_target) const + bindRegex + + string & + p_target + + +Set targeted variable to option's parameter regex. + + + +p_target + + +Reference variable + + +generated option callback + + + + + error + initialize + + + + + typename T + + + t_callback + t_callback xtd::Application::bindCallback + (T p_action) const + bindCallback + + T + p_action + + +Associate option to given generic callback. + + + +p_action + + +function compatible withstd::function<void(void)> + signature + + +generated option callback + + + + + Application + initialize + + + + + typename T + + + t_callback + t_callback xtd::Application::bindValueIfGiven + (T &p_target, const T &p_value) const + bindValueIfGiven + + T & + p_target + + + const T & + p_value + + +Set given value to referenced variable if option is given. + + + +p_target + + +Reference variable + + + +p_value + + +Value to set to p_target + + +generated option callback + + + + + initialize + + + + + typename T + + + t_callback + t_callback xtd::Application::bindNumber + (T &p_target, T p_min=std::numeric_limits< T >::min(), T p_max=std::numeric_limits< T >::max()) const + bindNumber + + T & + p_target + + + T + p_min + std::numeric_limits< T >::min() + + + T + p_max + std::numeric_limits< T >::max() + + +Bind option's paramter as number to referenced variable. + + + +p_target + + +Reference variable + + + +p_min + + +Minimum parameter value [inclusive] + + + +p_max + + +Maximum parameter value [inclusive] + + +generated option callback +Will led to an error if input parameter : +not lexically convertible to Tis lower than p_minis greater than p_max + + + + + initialize + + + + + typename T + + + class Iterable + + + t_callback + t_callback xtd::Application::bindValues + (T &p_target, const Iterable &p_values) const + bindValues + + T & + p_target + + + const Iterable & + p_values + + +Bind option's parameter to target variable and checks among authorized values. + + + +p_target + + +Reference variable + + + +p_values + + +Iterable container of authorized option's values + + +generated option callback + + + + + Application + initialize + + + + + class TCollection + + + t_callback + t_callback xtd::Application::bindAccumulator + (TCollection &p_target) const + bindAccumulator + + TCollection & + p_target + + +Append option's parameter to target container for each command line hit. + + + +p_target + + +Reference variable container + + +generated option callback + + + + + initialize + + + + + typename... + Arguments + Arguments + + + void + void xtd::Application::error_nohelp + (int p_code, const string &p_format, Arguments &&...p_args) const + error_nohelp + + int + p_code + + + const string & + p_format + + + Arguments &&... + p_args + + +Prints error on standard error and exit. + + + +p_code + + +exit code + + + +p_format + + +error message format (boost::format compatible) + + + +p_args + + +Template variadic arguments to format + + + + + + + execute + initialize + + + + + typename... + Arguments + Arguments + + + void + void xtd::Application::error + (int p_code, const string &p_format, Arguments &&...p_args) const + error + + int + p_code + + + const string & + p_format + + + Arguments &&... + p_args + + +Prints error and usage on standard error and exit. + + + +p_code + + +exit code + + + +p_format + + +error message format (boost::format compatible) + + + +p_args + + +Template variadic arguments to format + + + + + + + bindFile + bindDir + bindRegex + addOption + initialize + + + + + typename... + Arguments + Arguments + + + void + void xtd::Application::warn + (const string &p_format, Arguments &&...p_args) const + warn + + const string & + p_format + + + Arguments &&... + p_args + + +Prints a warning on standard error. + + + +p_format + + +error message format (boost::format compatible) + + + +p_args + + +Template variadic arguments to format + + + + + + + initialize + + + + + void + void xtd::Application::readArgs + (int p_argc, const char *const p_argv[]) + readArgs + + int + p_argc + + + const char *const + p_argv + [] + + + + + +Reset getopt_long internal global variable. + + + + + + + void + void xtd::Application::usage + (std::ostream &p_stream=std::cerr) const + usage + + std::ostream & + p_stream + std::cerr + + + + + + + + + + + void + void xtd::Application::handleSignal + (const boost::system::error_code &p_error, int p_signalNumber) + handleSignal + + const boost::system::error_code & + p_error + + + int + p_signalNumber + + + + + +Mysteriously appeared here. Commented until any bug pops back. + + + + + + + +Parses arguments from main(int,char**) function. + + +IntrocutionHow to useExecution flowBindind options + +Introcution +This object provides a default application skeleton including : +user-friendly wrapping of argument parsing (on top of C's getopt_long)usage generator for declared optionsdefault options : help and log-leveldefault logging facilitysignal handling on a separate threadbinary RCS identity tracking, usually generated by xtd compile-time dependency tracking system + + +How to use + +Inherit your class from ApplicationDefine the mandatory method Application::processDeclare your program arguments in your constructorOptionaly parse your configuration, check your parameters and initialize your application in the dedicated methodsInstantiate your class in main(int,char**) function and all Application::execute method +Typical example : namespacextd{ +namespace{ + + +classMyApp:publicApplication +{ +public: +MyApp(void): +Application() +{ +addOption('i',"input-file", +argument::mandatory, +requirement::mandatory, +"processgivenfile", +Application::bindFile(m_inputFile,true)); +} + +private: +intprocess(void) +{ +std::cout<<"givenreadablefile:"<<m_inputFile<<std::endl; + +//dosomestuff +return(l_success)?0:1; +} + +private: +std::stringm_inputFile; +}; + + +intmain(intp_argc,char**p_argv) +{ +MyAppl_app; +returnl_app.execute(p_argc,p_argv); +} + +} + + +Execution flow + +(*) --> initialize default logging (level::crit) + --> read arguments from argc & argv + --> parseConfig() + note right: User may redefine this function + --> checkOptions() + --> initialize logging with parsed level + note right: User may redefine this function + --> initialize() + note right: User may redefine this function + --> ===B1=== + --> start signal thread + --> stop & join signal thread + --> ===B2=== + ===B1=== --> process() + note left: User <b>must</b> re-define this function + --> ===B2=== + --> (*) + + + +Bindind options + +bindDir : Bind option's parameter to a directory.bindFile : Bind option's parameter to a file name.bindGiven : Set targeted variable to true if option is given on command line.bindString : Set targeted variable to option's parameter.bindCallback : Associate option to given generic callback.bindValueIfGiven : Set given value to referenced variable if option is given.bindNumber : Bind option's paramter as number to referenced variable.bindValues : Bind option's parameter to target variable and checks among authorized values.bindAccumulator : Append option's parameter to target container for each command line hit. + + + + + + + + + + + m_logLevel + + + m_remainingArgs + + + m_rcsid + m_binName + + + + + + + + + + + + xtd::Application::TestApplication + xtd::ApplicationaddHelpMsg + xtd::ApplicationaddOption + xtd::ApplicationaddSignalHandler + xtd::ApplicationApplication + xtd::Applicationargument + xtd::ApplicationbindAccumulator + xtd::ApplicationbindCallback + xtd::ApplicationbindDir + xtd::ApplicationbindFile + xtd::ApplicationbindGiven + xtd::ApplicationbindNumber + xtd::ApplicationbindRegex + xtd::ApplicationbindString + xtd::ApplicationbindValueIfGiven + xtd::ApplicationbindValues + xtd::ApplicationcheckOptions + xtd::Applicationerror + xtd::Applicationerror_nohelp + xtd::Applicationexecute + xtd::ApplicationgetVersion + xtd::ApplicationhandleSignal + xtd::Applicationinitialize + xtd::ApplicationisOptionGiven + xtd::Applicationm_binName + xtd::Applicationm_disableCatch + xtd::Applicationm_disableExit + xtd::Applicationm_helpText + xtd::Applicationm_ioService + xtd::Applicationm_logLevel + xtd::Applicationm_optionList + xtd::Applicationm_rcsid + xtd::Applicationm_remainingArgs + xtd::Applicationm_runThread + xtd::Applicationm_signalHandlerMap + xtd::Applicationm_signals + xtd::Applicationm_work + xtd::ApplicationparseConfig + xtd::Applicationprocess + xtd::ApplicationreadArgs + xtd::Applicationrequirement + xtd::Applicationt_callback + xtd::Applicationt_option_list + xtd::Applicationt_sig_handler + xtd::Applicationusage + xtd::Applicationwarn + xtd::Application~Application + + + diff --git a/coverxygen/test/data/valid.xml b/coverxygen/test/data/valid.xml new file mode 100644 index 0000000..321b639 --- /dev/null +++ b/coverxygen/test/data/valid.xml @@ -0,0 +1,668 @@ + + + xtd::log::Appender + t_filter + t_filters + m_formatter + m_filters + Appender + ~Appender + setFormatter + addFilter + clearFilters + clearFormatter + log + print + + xtd::Application + argument + mandatory + none + requirement + optional + mandatory + t_sig_handler + t_callback + t_option_list + ::TestApplication + m_binName + m_logLevel + m_remainingArgs + m_rcsid + m_disableExit + m_disableCatch + m_optionList + m_helpText + m_runThread + m_ioService + m_work + m_signals + m_signalHandlerMap + Application + ~Application + execute + parseConfig + checkOptions + initialize + process + addSignalHandler + getVersion + addOption + isOptionGiven + addHelpMsg + bindDir + bindFile + bindGiven + bindString + bindRegex + bindCallback + bindValueIfGiven + bindNumber + bindValues + bindAccumulator + error_nohelp + error + warn + readArgs + usage + handleSignal + + xtd::tty::attrs + s + reset + bold + dim + underlined + blink + reverse + hidden + unset + reset + dim + bold + underlined + blink + reverse + hidden + m_attrs + attrs + attrs + from_string + to_string + operator| + operator|= + operator== + getValues + isSet + + xtd::log::AutoWidth + m_widths + m_fields + AutoWidth + AutoWidth + operator() + + xtd::mixins::Cloneable + Parent + Cloneable + ~Cloneable + clone + + xtd::mixins::CloneableBase + Parent + clone + + xtd::tty::color + c + unset + black + red + green + yellow + blue + magenta + cyan + gray + normal + lblack + lred + lgreen + lyellow + lblue + lmagenta + lcyan + lgray + white + unset + normal + black + red + green + yellow + blue + magenta + cyan + gray + lblack + lred + lgreen + lyellow + lblue + lmagenta + lcyan + lgray + white + m_value + m_extended + color + color + operator== + isExtended + getValue + isSet + from_string + to_string + + xtd::log::ColoredFormatter + t_styles + t_style_modifier + ConfLoader + ::TestColoredFormatter + m_fieldStyles + m_styleModifiers + ColoredFormatter + ColoredFormatter + ~ColoredFormatter + setAttrs + setFgColor + setBgColor + setStyle + setStyle + setStyles + addStyleModifier + create + createFields + getFulllog + + xtd::log::ConfLoader + t_properties + t_appender_constructor + t_formatter_constructor + ::TestConfLoader + m_appenders + m_formatters + m_appenderConstructors + m_formatterConstructors + configure + registerAppender + registerFormatter + getFormatter + getAppender + createLogger + + xtd::error + m_message + error + ~error + what + + xtd::log::Fields + this_type + m_name + m_module + m_threadid + m_slevel + m_ilevel + m_message + m_filename + m_line + m_function + m_location + m_time + m_fulllog + m_pid + m_ppid + m_functors + Fields + get + exists + get + set + + xtd::format + vargs + vargs + vargs_noexcept + + xtd::log::FormattedRecord + m_hasLoc + m_fulllog + m_message + m_function + m_filename + m_line + FormattedRecord + FormattedRecord + + xtd::log::Formatter + t_formats + t_format_modifier + ConfLoader + ::TestFormatter + m_fmt + m_locFmt + m_timeFmt + m_fieldFormats + m_formatModifiers + Formatter + Formatter + ~Formatter + setLayout + setLayouts + setFieldFormat + setFieldFormats + addFormatModifier + format + create + createFields + getFulllog + resolveLayout + resolveTime + + xtd::log::log_error + + xtd::log::Logger + t_appenders + ::TestLogger + m_module + m_name + m_level + m_appenders + m_mutex + Logger + Logger + Logger + ~Logger + getName + getLevel + setName + setLevel + addAppender + clearAppenders + emerg + alert + crit + err + warning + notice + info + debug + log + getAppenders + + xtd::log::MemoryAppender + ConfLoader + ::TestMemoryAppender + m_maxRecords + m_records + m_mutex + MemoryAppender + ~MemoryAppender + copyRecords + print + create + + xtd::config::parse_error + + xtd::config::Parser + t_refs + t_data + t_result + m_data + m_params + Parser + Parser + Parser + get + get + get + get + get_default + parse + parse + parse + search + translate + resolveExt + resolveRefs + extractKey + + xtd::mixins::PureCloneableBase + Parent + clone + + xtd::log::Record + m_name + m_module + m_level + m_format + m_time + m_threadID + m_pid + m_ppid + Record + + xtd::log::RootLogger + t_loggers + t_levels + ::TestLogger + m_loggers + m_mutex + RootLogger + RootLogger + get + getLevels + initialize + setAllLevels + setAllValueLevels + updateLevels + getLevelTo + setLevelTo + emergTo + alertTo + critTo + errTo + warningTo + infoTo + noticeTo + debugTo + getClosestParent + + xtd::mixins::Singleton + ms_instance + Singleton + Singleton + get + + xtd::log::Stream + m_ref + m_stream + ms_mutex + Stream + Stream + flush + operator<< + + xtd::log::StreamAppender + ConfLoader + ::TestStreamAppender + m_stream + StreamAppender + StreamAppender + ~StreamAppender + create + print + + xtd::tty::style + m_fg + m_bg + m_attrs + style + style + getFgColor + getBgColor + getAttrs + setFgColor + setBgColor + setAttrs + + xtd::log::StyleByLevel + m_conds + StyleByLevel + operator() + + xtd::log::StyleMatch + m_match + m_style + StyleMatch + operator() + + xtd::log::SyslogAppender + ConfLoader + ::TestSyslogAppender + m_opts + m_facility + ms_instances + ms_mutex + SyslogAppender + ~SyslogAppender + to_facility + to_option + create + print + + xtd::Application::t_option + m_shortOpt + m_longOpt + m_description + m_argumentType + m_callback + m_status + m_given + + xtd::tty::Text + m_str + m_style + Text + Text + Text + str + operator string + + xtd::log::Logger::thread_counter + ms_ids + ms_mutex + ms_mainID + get_id + + xtd::text::xml + t_entity + t_data + ms_entityList + encode + encode_copy + initialize + + xtd + status + ok + error + timeout + notfound + next + operator<< + valueof + to_enum + + xtd::config + xtd_error_class + + xtd::config::impl + + xtd::log + level + emerg + alert + crit + err + warning + notice + info + debug + from + from + to_value + to_string + is_valid + is_valid + getRoot + operator<< + operator<< + emerg + alert + crit + err + warning + info + notice + debug + raise + xtd_error_class + + xtd::log::@21 + + xtd::mixins + + xtd::text + + xtd::tty + operator<< + operator<< + operator<< + + config.hh + + core.hh + + exemple1.cc + main + + formatter.cc + main + + Application.cc + rcsid + + Application.hh + + Application.hh + + Grammar.cc + + Grammar.hh + + Parser.cc + + Parser.hh + + types.hh + + types.hh + sptr + fn + + error.hh + xtd_error_class + + format.hh + + log.hh + + log.hh + + Appender.cc + + Appender.hh + + ColoredFormatter.cc + + ColoredFormatter.hh + + ConfLoader.cc + + ConfLoader.hh + XTD_CORE_LOG_REGISTER_APPENDER + XTD_CORE_LOG_REGISTER_FORMATTER + + Fields.hh + + FormatModifiers.cc + + FormatModifiers.hh + + Formatter.cc + + Formatter.hh + + fwd.hh + + helpers.cc + + helpers.hh + + Logger.cc + + Logger.hh + + logtypes.hh + HERE + + MemoryAppender.cc + + MemoryAppender.hh + + Stream.cc + + Stream.hh + + StreamAppender.cc + + StreamAppender.hh + + SyslogAppender.cc + + SyslogAppender.hh + + cloneable.hh + + singleton.hh + + text.cc + + text.hh + + tty.cc + + tty.hh + + tty.hh + + types.cc + + core_config + + core_log + + core_tty + + todo + + /home/psyco/dev/xtdcpp/core/src/config + + /home/psyco/dev/xtdcpp/core + + /home/psyco/dev/xtdcpp/core/doc + + /home/psyco/dev/xtdcpp/core/doc/example + + /home/psyco/dev/xtdcpp/core/src/log + + /home/psyco/dev/xtdcpp/core/doc/example/log + + /home/psyco/dev/xtdcpp/core/src/mixins + + /home/psyco/dev/xtdcpp/core/src + + index + + diff --git a/coverxygen/test/test_coverxygen.py b/coverxygen/test/test_coverxygen.py new file mode 100644 index 0000000..5bc91ac --- /dev/null +++ b/coverxygen/test/test_coverxygen.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 +#------------------------------------------------------------------# + +__author__ = "Xavier MARCELET " + +#------------------------------------------------------------------# + +import os +import tempfile +import xml.etree.ElementTree as ET +import unittest2 as unittest +from coverxygen import Coverxygen + +#------------------------------------------------------------------# + +class CoverxygenTest(unittest.TestCase): + def __init__(self, *p_args, **p_kwds): + super(CoverxygenTest, self).__init__(*p_args, **p_kwds) + l_path = os.path.realpath(os.path.dirname(__file__)) + self.m_dataRoot = os.path.join(l_path, "data") + + def get_data_path(self, p_file, p_assert=True): + l_path = os.path.join(self.m_dataRoot, p_file) + if p_assert: + self.assertEqual(True, os.path.exists(l_path)) + return l_path + + def test_error(self): + with self.assertRaisesRegex(RuntimeError, "message arg"): + Coverxygen.error("message %s", "arg") + l_expected = r"invalid message format 'message %s' with args '\(\)'" + with self.assertRaisesRegex(RuntimeError, l_expected): + Coverxygen.error("message %s") + + def test_get_index_path_from_root(self): + with self.assertRaises(RuntimeError): + Coverxygen.get_index_path_from_root(".") + l_dir = tempfile.mkdtemp() + l_path = os.path.join(l_dir, "index.xml") + l_file = open(l_path, "w") + l_file.close() + self.assertEqual(l_path, Coverxygen.get_index_path_from_root(l_dir)) + os.unlink(l_path) + os.removedirs(l_dir) + + def test_get_file_path_from_root(self): + with self.assertRaises(RuntimeError): + Coverxygen.get_file_path_from_root(".", "base") + l_dir = tempfile.mkdtemp() + l_path = os.path.join(l_dir, "base.xml") + l_file = open(l_path, "w") + l_file.close() + self.assertEqual(l_path, Coverxygen.get_file_path_from_root(l_dir, "base")) + os.unlink(l_path) + os.removedirs(l_dir) + + def test_get_xmldoc_from_file(self): + with self.assertRaises(RuntimeError): + Coverxygen.get_xmldoc_from_file("invalid_path") + l_regex = "invalid.xml : mismatched tag: line 667, column 2" + with self.assertRaisesRegex(RuntimeError, l_regex): + Coverxygen.get_xmldoc_from_file(self.get_data_path("invalid.xml")) + Coverxygen.get_xmldoc_from_file(self.get_data_path("valid.xml")) + + def test_extract_name(self): + l_data = """ + + + def + name + + + name + + + + + """ + l_doc = ET.fromstring(l_data) + self.assertEqual("def", Coverxygen.extract_name(l_doc.find("./node1"))) + self.assertEqual("name", Coverxygen.extract_name(l_doc.find("./node2"))) + self.assertEqual("id3", Coverxygen.extract_name(l_doc.find("./node3"))) + with self.assertRaisesRegex(RuntimeError, "mytag"): + Coverxygen.extract_name(l_doc.find("./node4")) + + def test_extract_documented(self): + l_data = """ + + + + + + + + content + + + + content + + + content + + + """ + l_doc = ET.fromstring(l_data) + self.assertEqual(False, Coverxygen.extract_documented(l_doc.find("./node1"))) + self.assertEqual(False, Coverxygen.extract_documented(l_doc.find("./node2"))) + self.assertEqual(True, Coverxygen.extract_documented(l_doc.find("./node3"))) + self.assertEqual(True, Coverxygen.extract_documented(l_doc.find("./node4"))) + self.assertEqual(True, Coverxygen.extract_documented(l_doc.find("./node5"))) + + + def test_extract_location(self): + l_data = """ + + + + + + + + + + + + + + + + """ + l_doc = ET.fromstring(l_data) + + l_line = 1 + l_path = "file.hh" + l_file = "/tmp/file.hh" + l_node = l_doc.find("./node1") + self.assertEqual((l_file, l_line), Coverxygen.extract_location(l_node, l_path, "/tmp")) + + l_line = 1 + l_path = "/root/file.hh" + l_file = "/root/file.hh" + l_node = l_doc.find("./node1") + self.assertEqual((l_file, l_line), Coverxygen.extract_location(l_node, l_path, "/tmp")) + + l_line = 22 + l_path = "/root/file.hh" + l_file = "/tmp/actual.hh" + l_node = l_doc.find("./node2") + self.assertEqual((l_file, l_line), Coverxygen.extract_location(l_node, l_path, "/tmp")) + + l_line = 33 + l_path = "/root/file.hh" + l_file = "/opt/actual.hh" + l_node = l_doc.find("./node3") + self.assertEqual((l_file, l_line), Coverxygen.extract_location(l_node, l_path, "/tmp")) + + with self.assertRaises(RuntimeError): + l_node = l_doc.find("./error1") + Coverxygen.extract_location(l_node, l_path, "/tmp") + + with self.assertRaises(RuntimeError): + l_node = l_doc.find("./error2") + Coverxygen.extract_location(l_node, l_path, "/tmp") + + + def test_get_absolute_path(self): + self.assertEqual("/root/file.hh", Coverxygen.get_absolute_path("file.hh", "/root")) + self.assertEqual("/root/file.hh", Coverxygen.get_absolute_path("./file.hh", "/root")) + self.assertEqual("/tmp/file.hh", Coverxygen.get_absolute_path("/tmp/file.hh", "/root")) + self.assertEqual("/file.hh", Coverxygen.get_absolute_path("../file.hh", "/root")) + self.assertEqual("/file.hh", Coverxygen.get_absolute_path("/../file.hh", "/root")) + + def test_should_filter_out(self): + l_xml = """ + + + + + + + + """ + l_doc = ET.fromstring(l_xml) + l_scopes = ["s1", "s2"] + l_kinds = ["k1", "k2"] + l_obj = Coverxygen(None, None, l_scopes, l_kinds, "/p", None, None, False) + self.assertEqual(False, l_obj.should_filter_out(l_doc.find("./node1"), "/p/file.hh", 1)) + self.assertEqual(False, l_obj.should_filter_out(l_doc.find("./node2"), "/p/file.hh", 1)) + self.assertEqual(True, l_obj.should_filter_out(l_doc.find("./node3"), "/p/file.hh", 1)) + self.assertEqual(True, l_obj.should_filter_out(l_doc.find("./node4"), "/p/file.hh", 1)) + self.assertEqual(True, l_obj.should_filter_out(l_doc.find("./node5"), "/p/file.hh", 1)) + self.assertEqual(True, l_obj.should_filter_out(l_doc.find("./node1"), "/other/file.hh", 1)) + + def test_process_symbol(self): + l_doc = ET.parse(self.get_data_path("real.xml")) + l_scopes = ["private", "protected", "public"] + l_kinds = ["function", "class", "enum"] + l_node = l_doc.find("./compounddef//memberdef[@id='classxtd_1_1Application_1a672c075ed901e463609077d571a714c7']") + l_obj = Coverxygen(None, None, l_scopes, l_kinds, "/opt", None, "/opt", False) + l_data = l_obj.process_symbol(l_node, "/opt/file.hh") + l_expect = {'documented': True, 'line': 102, 'symbol': 'argument', 'file': '/opt/src/Application.hh'} + self.assertDictEqual(l_expect, l_data) + + l_node = l_doc.find("./compounddef//memberdef[@id='classxtd_1_1Application_1a907b6fe8247636495890e668530863d6']") + l_data = l_obj.process_symbol(l_node, "/opt/file.hh") + l_expect = {} + self.assertDictEqual(l_expect, l_data) + + + def test_merge_symbol(self): + l_syms = [{ "file" : "a", "key1" : 1}] + l_res = {} + l_expect = { "a" : [ { "file" : "a", "key1" : 1} ] } + Coverxygen.merge_symbols(l_res, l_syms) + self.assertDictEqual(l_expect, l_res) + + l_syms = [ + { "file" : "a", "key1" : 1}, + { "file" : "a", "key2" : 2} + ] + l_expect = { + "a" : [ + { "file" : "a", "key1" : 1 }, + { "file" : "a", "key2" : 2 } + ] + } + l_res = {} + Coverxygen.merge_symbols(l_res, l_syms) + self.assertDictEqual(l_expect, l_res) + + l_syms = [ + { "file" : "b", "key1" : 1}, + { "file" : "c", "key2" : 2} + ] + l_expect = { + "b" : [{ "file" : "b", "key1" : 1 }], + "c" : [{ "file" : "c", "key2" : 2 }] + } + l_res = {} + Coverxygen.merge_symbols(l_res, l_syms) + self.assertDictEqual(l_expect, l_res) + + l_syms = [ + { "file" : "b", "key1" : 1}, + { "file" : "c", "key2" : 2} + ] + l_expect = { + "b" : [{ "file" : "b", "key0" : 0 }, { "file" : "b", "key1" : 1 }], + "c" : [{ "file" : "c", "key2" : 2 }] + } + l_res = { + "b" : [{ "file" : "b", "key0" : 0 }] + } + Coverxygen.merge_symbols(l_res, l_syms) + self.assertDictEqual(l_expect, l_res) + + l_syms = [] + l_expect = { + "b" : [{ "file" : "b", "key0" : 0 }], + } + l_res = { + "b" : [{ "file" : "b", "key0" : 0 }] + } + Coverxygen.merge_symbols(l_res, l_syms) + self.assertDictEqual(l_expect, l_res) + + + def test_process_file(self): + l_file = self.get_data_path("real.xml") + l_scopes = ["private", "protected", "public"] + l_kinds = ["enum"] + l_obj = Coverxygen(None, None, l_scopes, l_kinds, "/opt", None, "/opt", False) + l_res = {} + l_name = "/opt/src/Application.hh" + l_obj.process_file(l_file, l_res) + self.assertEqual(1, len(l_res.keys())) + self.assertEqual(True, l_name in l_res) + self.assertEqual(2, len(l_res[l_name])) + self.assertEqual(2, len([x for x in l_res[l_name] if x['documented']])) + + l_file = self.get_data_path("real.xml") + l_scopes = ["private", "protected", "public"] + l_kinds = ["class"] + l_obj = Coverxygen(None, None, l_scopes, l_kinds, "/opt", None, "/opt", False) + l_res = {} + l_name = "/opt/src/Application.hh" + l_obj.process_file(l_file, l_res) + self.assertEqual(1, len(l_res.keys())) + self.assertEqual(True, l_name in l_res) + self.assertEqual(1, len(l_res[l_name])) + self.assertEqual(1, len([x for x in l_res[l_name] if x['documented']])) diff --git a/debian/changelog b/debian/changelog index b4bcdfd..d9eb395 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,18 @@ -coverxygen (1.2.0ppa1~xenial) trusty; urgency=medium +coverxygen (1.3.0ppa1~xenial) xenial; urgency=medium + + * strengthen and sanitize code + * replaces --json option by --format + + -- Xavier MARCELET Tue, 07 Mar 2017 00:21:55 +0100 + +coverxygen (1.3.0ppa1~trusty) trusty; urgency=medium + + * strengthen and sanitize code + * replaces --json option by --format + + -- Xavier MARCELET Tue, 07 Mar 2017 00:21:33 +0100 + +coverxygen (1.2.0ppa1~xenial) xenial; urgency=medium * compatibility with doxygen strip path diff --git a/devtools/coverage.sh b/devtools/coverage.sh new file mode 100755 index 0000000..fa46b47 --- /dev/null +++ b/devtools/coverage.sh @@ -0,0 +1,38 @@ +#!/bin/bash + + +l_path=$(readlink -f $0) +cd $(dirname $(dirname ${l_path})) +l_interpreter=$1 + +function join +{ + local IFS="$1"; shift; + echo "$*"; +} + +function get_sources +{ + l_sources=$(find coverxygen -name '*.py' -a ! -name 'test_*') + echo ${l_sources} +} + +function get_source_dirs +{ + l_sources=$(get_sources) + l_dirs=() + for c_file in $(echo ${l_sources}); do + l_dirs=(${l_dirs[@]} $(dirname ${c_file})) + done + l_list=$(echo ${l_dirs[@]} | sed 's/ /\n/g' | sort -u | tr '\n' ' ') + join , ${l_list} +} + +function gen_cov +{ + rm -f .coverage + $1 -m coverage run --source "$(get_source_dirs)" --omit "coverxygen/test/*" --branch ./devtools/unittests.py +} + + +gen_cov ${l_interpreter} diff --git a/devtools/statuses.py b/devtools/statuses.py new file mode 100755 index 0000000..a725c7c --- /dev/null +++ b/devtools/statuses.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python +#------------------------------------------------------------------# + +__author__ = "Xavier MARCELET " + +#------------------------------------------------------------------# + +import json +import os +import sys +import requests +import argparse +import subprocess +import urllib +import hashlib + +l_path = os.path.realpath(os.path.dirname(__file__)) +os.chdir(os.path.dirname(l_path)) +sys.path.append(".") + +#------------------------------------------------------------------# + +class StatusHelper: + def __init__(self): + self.m_dryrun = False + self.m_parser = argparse.ArgumentParser("coverxygen build checker") + self.m_parser.add_argument("--token", help="Github API secret token", dest="m_token", required=True) + self.m_parser.add_argument("--build-id", help="Travis build-id", dest="m_buildID", required=True) + self.m_parser.add_argument("--commit", help="Current git commit hash", dest="m_commit", required=True) + self.m_parser.add_argument("--pull-id", help="Current pull request, false if not a PR", dest="m_prid", required=True) + self.m_parser.add_argument("--dry-run", help="Do not push statuses to github", dest="m_dryrun", action="store_true") + self.m_parser.parse_args(sys.argv[1:], self) + self.m_comment = "" + l_md5 = hashlib.md5() + l_md5.update(self.m_token.encode('utf-8')) + print("build-id : %s" % str(self.m_buildID)) + print("commit : %s" % str(self.m_commit)) + print("pull-id : %s" % str(self.m_prid)) + + def get_pr_commit(self): + if self.m_dryrun: + return {} + + l_params = { "access_token" : self.m_token } + l_headers = { "Content-Type" : "application/json" } + l_url = "https://api.github.com/repos/%(user)s/%(repo)s/pulls/%(prid)s" % { + "user" : "psycofdj", + "repo" : "coverxygen", + "prid" : self.m_prid + } + + try: + print("GET %s" % l_url) + l_req = requests.get(l_url, params=l_params, headers=l_headers) + except BaseException as l_error: + print("error while sending comment to github to %s : %s" % (l_url, str(l_error))) + sys.exit(1) + + try: + l_data = l_req.json() + return l_data["head"]["sha"] + except BaseException as l_error: + print("error while reading sha from '%s' : %s" % (str(l_data), str(l_error))) + sys.exit(1) + + def getTargetUrl(self): + l_url = "https://travis-ci.org/psycofdj/coverxygen/builds/%(buildID)s" + return l_url % { + "buildID" : self.m_buildID + } + + def make_badge(self, p_title, p_label, p_value, p_status, p_link = "#"): + if p_status == "error": + l_color = "red" + elif p_status == "failure": + l_color = "lightgrey" + elif p_status == "warning": + l_color = "yellow" + else: + l_color = "brightgreen" + l_url = "https://img.shields.io/badge/%(label)s-%(value)s-%(color)s.svg" % { + "label" : urllib.parse.quote(p_label), + "value" : urllib.parse.quote(p_value), + "color" : l_color + } + + return "[![%(title)s](%(url)s)](%(link)s)" % { + "title" : p_title, + "url" : l_url, + "link" : p_link + } + + def comment_pr(self, p_body): + if self.m_dryrun: + return {} + + l_params = { "access_token" : self.m_token } + l_headers = { "Content-Type" : "application/json" } + l_url = "https://api.github.com/repos/%(user)s/%(repo)s/issues/%(prid)s/comments" % { + "user" : "psycofdj", + "repo" : "coverxygen", + "prid" : self.m_prid + } + l_data = { + "body" : p_body + } + + try: + print("POST %s" % l_url) + l_req = requests.post(l_url, params=l_params, headers=l_headers, data=json.dumps(l_data)) + except BaseException as l_error: + print("error while sending comment to github") + print(str(l_error)) + sys.exit(1) + return l_req.json() + + def comment_commit(self, p_body): + if self.m_dryrun: + return {} + + l_params = { "access_token" : self.m_token } + l_headers = { "Content-Type" : "application/json" } + l_url = "https://api.github.com/repos/%(user)s/%(repo)s/commits/%(commitID)s/comments" % { + "user" : "psycofdj", + "repo" : "coverxygen", + "commitID" : self.m_commit + } + l_data = { + "body" : p_body + } + try: + print("POST %s" % l_url) + l_req = requests.post(l_url, params=l_params, headers=l_headers, data=json.dumps(l_data)) + except BaseException as l_error: + print("error while seding comment to github") + print(str(l_error)) + sys.exit(1) + return l_req.json() + + + def send_status(self, p_status, p_tag, p_description): + if self.m_dryrun: + return {} + l_url = "https://api.github.com/repos/%(user)s/%(repo)s/statuses/%(commit)s" % { + "user" : "psycofdj", + "repo" : "coverxygen", + "commit" : self.m_commit + } + l_params = { "access_token" : self.m_token } + l_headers = { "Content-Type" : "application/json" } + l_data = { + "state" : p_status, + "target_url" : self.getTargetUrl(), + "description" : p_description, + "context" : p_tag + } + + try: + print("POST %s" % l_url) + l_req = requests.post(l_url, params=l_params, headers=l_headers, data=json.dumps(l_data)) + except BaseException as l_error: + print("error while seding comment to github") + print(str(l_error)) + sys.exit(1) + return l_req.json() + + def run_unittests(self): + print("-------------------") + print("Running test suites") + print("-------------------") + + l_proc = subprocess.Popen(["python3", "./devtools/unittests.py", "--format", "json", "-v"], stdout=subprocess.PIPE) + l_proc.wait() + try: + l_lines = l_proc.stdout.read().decode("utf-8") + l_data = json.loads(l_lines) + l_info = { + "nbtests" : l_data["tests"], + "nbok" : l_data["success"], + "nbko" : l_data["tests"] - l_data["success"] + } + l_description = "Ran %(nbtests)d tests : %(nbok)d success, %(nbko)d errors" % l_info + l_status = "error" + + if l_info["nbko"] == 0: + l_status = "success" + for c_test in l_data["details"]["success"]: + print("oK : %(file)s:%(class)s:%(method)s" % c_test) + for c_test in l_data["details"]["errors"]: + print("Ko : %(file)s:%(class)s:%(method)s : %(message)s" % c_test) + for c_test in l_data["details"]["failures"]: + print("Ko : %(file)s:%(class)s:%(method)s : %(message)s" % c_test) + for c_test in l_data["details"]["expectedFailures"]: + print("Ko : %(file)s:%(class)s:%(method)s : %(message)s" % c_test) + for c_test in l_data["details"]["unexpectedSuccesses"]: + print("Ko : %(file)s:%(class)s:%(method)s" % c_test) + print("") + print("Ran %(nbtests)d tests, %(nbok)d success, %(nbko)d failures" % l_info) + l_badge = self.make_badge("Unit Tests", "unittests", "%(nbok)d / %(nbtests)d" % l_info, l_status) + self.m_comment += "%s\n" % l_badge + except Exception as l_error: + l_status = "failure" + l_description = "unexpected error while reading unittests results" + l_badge = self.make_badge("Unit Tests", "unittests", "failure", l_status) + self.m_comment += "%s\n" % l_badge + print("error while running unittests : %s" % (l_error)) + self.send_status(l_status, "checks/unittests", l_description) + print("") + + def run_pylint(self): + print("-------------------") + print("Running pylint ") + print("-------------------") + + l_proc = subprocess.Popen(["python3", "./devtools/xtdlint.py", "--rcfile", ".pylintrc", "-j", "4", "coverxygen" ], stdout=subprocess.PIPE) + l_proc.wait() + try: + l_lines = l_proc.stdout.read().decode("utf-8") + l_data = json.loads(l_lines) + l_score = l_data["report"]["score"] + l_nbErrors = l_data["report"]["errors"]["by_cat"].get("fatal", 0) + l_nbErrors += l_data["report"]["errors"]["by_cat"].get("error", 0) + + if l_score < 9: + l_status = "error" + l_description = "pylint score %.2f/10 too low" % l_score + elif l_nbErrors != 0: + l_status = "error" + l_description = "pylint detected '%d' unacceptables errors" % l_nbErrors + else: + l_status = "success" + l_description = "pylint score is %.2f/10" % l_score + for c_module, c_data in l_data["errors"].items(): + for c_msg in c_data["items"]: + c_msg["path"] = c_data["path"] + print("%(C)s:%(symbol)-20s %(path)s:%(line)d:%(column)d " % c_msg) + print("") + print("Final score : %.2f/10" % l_score) + l_badge = self.make_badge("PyLint", "pylint", "%.2f" % l_score, l_status) + self.m_comment += "%s\n" % l_badge + except Exception as l_error: + l_status = "failure" + l_description = "unexpected error while reading pylint results" + print("error while running pylint : %s" % (l_error)) + l_badge = self.make_badge("PyLint", "pylint", "failure", l_status) + self.m_comment += "%s\n" % l_badge + self.send_status(l_status, "checks/pylint", l_description) + print("") + + def run_sphinx(self): + print("------------------------") + print("Genrating documentation ") + print("------------------------") + + l_proc = subprocess.Popen(["sphinx-build", "-qaNW", "-b", "html", "-d", "../build/docs/cache", ".", "../build/docs/html" ], cwd="./docs", stderr=subprocess.PIPE) + l_proc.wait() + if l_proc.returncode != 0: + l_status = "error" + l_description = "error while generating documentation" + l_badge = self.make_badge("Documentation", "doc", "failed", l_status) + self.m_comment += "%s\n" % l_badge + else: + l_status = "success" + l_description = "documentation successfully generated" + l_badge = self.make_badge("Documentation", "doc", "passed", l_status) + self.m_comment += "%s\n" % l_badge + + l_lines = l_proc.stderr.readlines() + if len(l_lines): + for c_line in l_lines: + print(c_line.decode("utf-8")) + else: + print("") + print("Documentation OK") + + self.send_status(l_status, "checks/documentation", l_description) + print("") + + + def run(self): + if self.m_prid != "false" : + self.m_commit = self.get_pr_commit() + + self.send_status("pending", "checks/unittests", "running unittests...") + self.send_status("pending", "checks/pylint", "running pylint...") + #self.send_status("pending", "checks/documentation", "running sphinx...") + self.run_unittests() + self.run_pylint() + #self.run_sphinx() + + self.comment_commit("Automatic build report for commit %(commit)s:\n\n%(results)s" % { + "commit" : self.m_commit, + "results" : self.m_comment + }) + # if self.m_prid != "false" : + # self.comment_pr("Automatic build report for commit %(commit)s:\n\n%(results)s" % { + # "commit" : self.m_commit, + # "results" : self.m_comment + # }) + +if __name__ == "__main__": + l_app = StatusHelper() + l_app.run() diff --git a/devtools/unittests.py b/devtools/unittests.py new file mode 100755 index 0000000..7967608 --- /dev/null +++ b/devtools/unittests.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# -*- mode:python -*- +# -*- coding:utf-8 -*- +#------------------------------------------------------------------# + +__author__ = "Xavier MARCELET " + +#------------------------------------------------------------------# + +import inspect +import unittest2 as unittest +import importlib +import os +import sys +import json + +#------------------------------------------------------------------# + +def __sys_path(): + # l_path = os.path.realpath(os.path.dirname(__file__)) + # os.chdir(os.path.dirname(l_path)) + sys.path.insert(0, ".") + +__sys_path() + +#------------------------------------------------------------------# + +class JsonTestRunner(unittest.TextTestRunner): + def __init__(self, *p_args, **p_kwds): + self.m_origStream = p_kwds.get("stream", sys.stdout) + p_kwds["stream"] = open("/dev/null", "w") + super(JsonTestRunner, self).__init__(*p_args, **p_kwds) + + def _makeResult(self): + l_res = super(JsonTestRunner, self)._makeResult() + return l_res + + @staticmethod + def _getTestInfo(p_test, p_message=None): + l_file = inspect.getsourcefile(p_test.__class__) + l_methodName = p_test._testMethodName + l_method = getattr(p_test, l_methodName) + l_data = { + "file" : l_file, + "line" : inspect.getsourcelines(l_method)[1], + "class" : p_test.__class__.__name__, + "method" : l_methodName + } + if p_message is not None: + l_data["message"] = p_message + return l_data + + + def run(self, p_test): + if self.verbosity > 1: + l_allTests = [] + for c_item in p_test._tests: + if hasattr(c_item, "_tests"): + for c_test in c_item._tests: + l_allTests.append(c_test) + else: + l_allTests.append(c_item) + + l_res = super(JsonTestRunner, self).run(p_test) + l_data = { + "tests" : l_res.testsRun, + "success" : l_res.testsRun - len(l_res.errors) - len(l_res.failures), + "errors" : len(l_res.errors), + "failures" : len(l_res.failures), + "skipped" : len(l_res.skipped), + "expectedFailures" : len(l_res.expectedFailures), + "unexpectedSuccesses" : len(l_res.unexpectedSuccesses) + } + + if self.verbosity > 1: + l_errorTests = set([x for x in l_res.errors]) + l_errorTests = l_errorTests | set([x[0] for x in l_res.failures]) + l_errorTests = l_errorTests | set([x[0] for x in l_res.skipped]) + l_errorTests = l_errorTests | set([x[0] for x in l_res.expectedFailures]) + l_errorTests = l_errorTests | set([x for x in l_res.unexpectedSuccesses]) + l_successTests = [ x for x in l_allTests if not x in l_errorTests ] + l_data["details"] = { + "success" : [ self._getTestInfo(x) for x in l_successTests ], + "errors" : [ self._getTestInfo(x[0], x[1]) for x in l_res.errors ], + "failures" : [ self._getTestInfo(x[0], x[1]) for x in l_res.failures ], + "skipped" : [ self._getTestInfo(x[0], x[1]) for x in l_res.skipped ], + "expectedFailures" : [ self._getTestInfo(x[0], x[1]) for x in l_res.expectedFailures ], + "unexpectedSuccesses" : [ self._getTestInfo(x) for x in l_res.unexpectedSuccesses ], + } + + self.m_origStream.write(json.dumps(l_data)) + return l_res + +class XtdTestProgram(unittest.TestProgram): + def __init__(self, *p_args, **p_kwds): + self.format = None + super(XtdTestProgram, self).__init__(*p_args, **p_kwds) + + def _getParentArgParser(self): + l_parser = super(XtdTestProgram, self)._getParentArgParser() + l_parser.add_argument('--format', dest='format', action='store', help='Set output format : text or json') + return l_parser + + def runTests(self): + if self.format == "json": + self.testRunner = JsonTestRunner + l_parser = super(XtdTestProgram, self).runTests() + +class XtdTestLoader(unittest.TestLoader): + def __init__(self): + super(XtdTestLoader, self).__init__() + self.m_data = [] + self._loadTests() + + def _splitToPackage(self, p_file): + if p_file.startswith("./"): + p_file = p_file[2:] + l_dir, l_name = os.path.split(p_file) + l_module = l_name.split(".")[0] + l_package = l_dir.replace("/", ".") + return l_module, l_package + + def _loadTests(self): + l_files = [] + for c_root, c_dirs, c_files in os.walk("."): + l_files += [ os.path.join(c_root,x) for x in c_files + if x.startswith("test") and x.endswith(".py") ] + + for c_file in l_files: + l_tests = [] + l_source = c_file.replace("/test/", "/").replace("test_", "") + l_testModule, l_testPkg = self._splitToPackage(c_file) + l_srcModule, l_srcPkg = self._splitToPackage(l_source) + l_srcName = "%s.%s" % (l_srcPkg, l_srcModule) + l_testName = "%s.%s" % (l_testPkg, l_testModule) + + l_module = importlib.import_module(l_testName) + for c_className in [ x for x in dir(l_module) if x.endswith("Test") ]: + l_class = getattr(l_module, c_className) + l_testCase = self.loadTestsFromTestCase(l_class) + self.m_data.append({ + "test_module" : l_testName, + "test_package" : l_testModule, + "src_module" : l_srcName, + "src_package" : l_srcModule, + "test_class" : c_className, + "object" : l_testCase, + "cases" : [ x for x in l_testCase ] + }) + + @staticmethod + def _matchTest(p_name, p_item): + if p_name == p_item["test_module"] or \ + p_name == p_item["test_package"] or \ + p_name == p_item["test_class"] or \ + p_name == p_item["src_module"] or \ + p_name == p_item["src_package"] or \ + p_name == "%s.%s" % (p_item["test_module"], p_item["test_class"]): + return True + + for c_case in p_item["cases"]: + l_name = c_case._testMethodName + l_fullName = "%s.%s.%s" % (p_item["test_module"], p_item["test_class"], l_name) + if p_name == l_name or p_name == l_fullName: + return True + + return False + + def loadTestsFromName(self, p_name, p_module=None): + l_suite = unittest.TestSuite() + for c_item in self.m_data: + if self._matchTest(p_name, c_item): + l_suite.addTests(c_item["object"]) + return l_suite + + def loadTestsFromModule(self, p_module, p_useLoadTests=True): + l_suite = unittest.TestSuite() + for c_item in self.m_data: + l_suite.addTests(c_item["object"]) + return l_suite + +if __name__ == "__main__": + l_loader = XtdTestLoader() + l_main = XtdTestProgram(testLoader=l_loader) diff --git a/devtools/xtdlint.py b/devtools/xtdlint.py new file mode 100755 index 0000000..e3adef9 --- /dev/null +++ b/devtools/xtdlint.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- mode:python -*- +# -*- coding:utf-8 -*- +#------------------------------------------------------------------# + +__author__ = "Xavier MARCELET " + +#------------------------------------------------------------------# + +import os +import sys +import pylint +import json + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter +from pylint.lint import Run, PyLinter + +#------------------------------------------------------------------# + +def __sys_path(): + l_path = os.path.realpath(os.path.dirname(__file__)) + sys.path.insert(0, os.path.dirname(l_path)) + +__sys_path() + +#------------------------------------------------------------------# + +class JsonReporter(BaseReporter): + """reports messages and layouts in plain text""" + + __implements__ = IReporter + name = 'text' + extension = 'txt' + line_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' + + def __init__(self, linter, output=None): + BaseReporter.__init__(self, output) + self.m_linter = linter + self.m_data = {} + + def handle_message(self, p_msg): + if not p_msg.module in self.m_data: + self.m_data[p_msg.module] = { + "abspath" : p_msg.abspath, + "path" : p_msg.path, + "items" : [] + } + + self.m_data[p_msg.module]["items"].append({ + "msg_id" : p_msg.msg_id, + "symbol" : p_msg.symbol, + "msg" : p_msg.msg, + "C" : p_msg.C, + "category" : p_msg.category, + "obj" : p_msg.obj, + "line" : p_msg.line, + "column" : p_msg.column + }) + + def _getStats(self): + l_categories = {} + l_messages = {} + for c_module in self.m_data.values(): + for c_item in c_module["items"]: + l_val = l_categories.get(c_item["category"], 0) + l_categories[c_item["category"]] = (l_val + 1) + l_val = l_messages.get(c_item["symbol"], 0) + l_messages[c_item["symbol"]] = (l_val + 1) + + return { + "by_cat" : l_categories, + "by_type" : l_messages + } + + + def _display(self, layout): + print(json.dumps({ + "report" : { + "cloc" : { + "total" : self.m_linter.stats['total_lines'], + "code" : self.m_linter.stats['code_lines'], + "empty" : self.m_linter.stats['empty_lines'], + "comment" : self.m_linter.stats['comment_lines'], + "docstring" : self.m_linter.stats['docstring_lines'], + "duplicated" : self.m_linter.stats['nb_duplicated_lines'], + "percent_duplicated" : self.m_linter.stats['percent_duplicated_lines'], + }, + "statement" : { + "total" : self.m_linter.stats['statement'], + "class" : self.m_linter.stats['class'], + "function" : self.m_linter.stats['function'], + "module" : self.m_linter.stats['module'], + "method" : self.m_linter.stats['method'], + }, + "undoc" : { + "module" : self.m_linter.stats['undocumented_module'], + "function" : self.m_linter.stats['undocumented_function'], + "class" : self.m_linter.stats['undocumented_class'], + "method" : self.m_linter.stats['undocumented_method'], + }, + "score" : self.m_linter.stats['global_note'], + "errors" : self._getStats() + }, + "errors" : self.m_data + })) + + +class XtdLint(PyLinter): + def __init__(self, *p_args, **p_kwds): + super(XtdLint, self).__init__(*p_args, **p_kwds) + self.set_reporter(JsonReporter(self)) + +if __name__ == "__main__": + Run.LinterClass = XtdLint + Run(sys.argv[1:]) diff --git a/requirements.dev.txt b/requirements.dev.txt new file mode 100644 index 0000000..6d3484d --- /dev/null +++ b/requirements.dev.txt @@ -0,0 +1,6 @@ +unittest2 +requests +coverage +coveralls +pylint +pytest From c1ea3c2879ed903def8b5cf6310bc15e00bba215 Mon Sep 17 00:00:00 2001 From: Xavier MARCELET Date: Tue, 7 Mar 2017 17:52:48 +0100 Subject: [PATCH 2/2] fix documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e25cf6e..9fce3f1 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ python -m coverxygen --xml-path --output doc-coverage. Full usage : ``` -usage: coverxygen [-h] [--version] [--verbose] [--json JSON] [--format FORMAT] --xml-dir XML_DIR --output OUTPUT --root-dir ROOT_DIR [--prefix PREFIX] [--scope SCOPE] [--kind KIND] +usage: coverxygen [-h] [--version] [--verbose] [--json JSON] [--format FORMAT] --xml-dir XML_DIR --output OUTPUT --src-dir ROOT_DIR [--prefix PREFIX] [--scope SCOPE] [--kind KIND] optional arguments: -h, --help show this help message and exit @@ -80,7 +80,7 @@ optional arguments: lcov : simpler json format --xml-dir XML_DIR path to generated doxygen XML directory --output OUTPUT destination output file (- for stdout) - --root-dir ROOT_DIR root source directory used to match prefix forrelative path generated files + --src-dir ROOT_DIR root source directory used to match prefix forrelative path generated files --prefix PREFIX keep only file matching given path prefix --scope SCOPE comma-separated list of items' scope to include : - public : public member elements