Thu, 17 Sep 2020 19:10:36 +0200
Third Party packages: updated coverage.py to 5.3.0.
--- a/docs/changelog Tue Sep 15 19:09:05 2020 +0200 +++ b/docs/changelog Thu Sep 17 19:10:36 2020 +0200 @@ -7,6 +7,7 @@ and allowing to navigate in the code - Third Party packages -- updated Pygments to 2.7.0 + -- updated coverage.py to 5.3.0 Version 20.9: - bug fixes
--- a/eric6/DebugClients/Python/coverage/backunittest.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/backunittest.py Thu Sep 17 19:10:36 2020 +0200 @@ -18,7 +18,7 @@ `unittest` doesn't have them. """ - # pylint: disable=arguments-differ, deprecated-method + # pylint: disable=signature-differs, deprecated-method if not unittest_has('assertCountEqual'): def assertCountEqual(self, *args, **kwargs):
--- a/eric6/DebugClients/Python/coverage/backward.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/backward.py Thu Sep 17 19:10:36 2020 +0200 @@ -9,6 +9,8 @@ import os import sys +from datetime import datetime + from coverage import env @@ -217,6 +219,17 @@ return self.__dict__ == other.__dict__ +def format_local_datetime(dt): + """Return a string with local timezone representing the date. + If python version is lower than 3.6, the time zone is not included. + """ + try: + return dt.astimezone().strftime('%Y-%m-%d %H:%M %z') + except (TypeError, ValueError): + # Datetime.astimezone in Python 3.5 can not handle naive datetime + return dt.strftime('%Y-%m-%d %H:%M') + + def invalidate_import_caches(): """Invalidate any import caches that may or may not exist.""" if importlib and hasattr(importlib, "invalidate_caches"):
--- a/eric6/DebugClients/Python/coverage/cmdline.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/cmdline.py Thu Sep 17 19:10:36 2020 +0200 @@ -18,7 +18,7 @@ from coverage import env from coverage.collector import CTracer from coverage.data import line_counts -from coverage.debug import info_formatter, info_header +from coverage.debug import info_formatter, info_header, short_stack from coverage.execfile import PyRunner from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding from coverage.results import should_fail_under @@ -85,6 +85,11 @@ "which isn't done by default." ), ) + sort = optparse.make_option( + '--sort', action='store', metavar='COLUMN', + help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " + "Default is name." + ) show_missing = optparse.make_option( '-m', '--show-missing', action='store_true', help="Show line numbers of statements in each module that weren't executed.", @@ -93,6 +98,10 @@ '--skip-covered', action='store_true', help="Skip files with 100% coverage.", ) + no_skip_covered = optparse.make_option( + '--no-skip-covered', action='store_false', dest='skip_covered', + help="Disable --skip-covered.", + ) skip_empty = optparse.make_option( '--skip-empty', action='store_true', help="Skip files with no code.", @@ -146,6 +155,13 @@ "to be run as 'python -m' would run it." ), ) + precision = optparse.make_option( + '', '--precision', action='store', metavar='N', type=int, + help=( + "Number of digits after the decimal point to display for " + "reported coverage percentages." + ), + ) rcfile = optparse.make_option( '', '--rcfile', action='store', help=( @@ -203,12 +219,14 @@ omit=None, contexts=None, parallel_mode=None, + precision=None, pylib=None, rcfile=True, show_missing=None, skip_covered=None, skip_empty=None, show_contexts=None, + sort=None, source=None, timid=None, title=None, @@ -331,10 +349,13 @@ "debug", GLOBAL_ARGS, usage="<topic>", description=( - "Display information on the internals of coverage.py, " + "Display information about the internals of coverage.py, " "for diagnosing problems. " - "Topics are 'data' to show a summary of the collected data, " - "or 'sys' to show installation information." + "Topics are: " + "'data' to show a summary of the collected data; " + "'sys' to show installation information; " + "'config' to show the configuration; " + "'premain' to show what is calling coverage." ), ), @@ -358,8 +379,10 @@ Opts.ignore_errors, Opts.include, Opts.omit, + Opts.precision, Opts.show_contexts, Opts.skip_covered, + Opts.no_skip_covered, Opts.skip_empty, Opts.title, ] + GLOBAL_ARGS, @@ -395,8 +418,11 @@ Opts.ignore_errors, Opts.include, Opts.omit, + Opts.precision, + Opts.sort, Opts.show_missing, Opts.skip_covered, + Opts.no_skip_covered, Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", @@ -430,6 +456,7 @@ Opts.include, Opts.omit, Opts.output_xml, + Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", description="Generate an XML report of coverage results." @@ -583,6 +610,8 @@ show_missing=options.show_missing, skip_covered=options.skip_covered, skip_empty=options.skip_empty, + precision=options.precision, + sort=options.sort, **report_args ) elif options.action == "annotate": @@ -594,11 +623,15 @@ skip_covered=options.skip_covered, skip_empty=options.skip_empty, show_contexts=options.show_contexts, + precision=options.precision, **report_args ) elif options.action == "xml": outfile = options.outfile - total = self.coverage.xml_report(outfile=outfile, **report_args) + total = self.coverage.xml_report( + outfile=outfile, skip_empty=options.skip_empty, + **report_args + ) elif options.action == "json": outfile = options.outfile total = self.coverage.json_report( @@ -617,6 +650,10 @@ fail_under = self.coverage.get_option("report:fail_under") precision = self.coverage.get_option("report:precision") if should_fail_under(total, fail_under, precision): + msg = "total of {total:.{p}f} is less than fail-under={fail_under:.{p}f}".format( + total=total, fail_under=fail_under, p=precision, + ) + print("Coverage failure:", msg) return FAIL_UNDER return OK @@ -749,7 +786,6 @@ print(" %s" % line) elif info == "premain": print(info_header("premain")) - from coverage.debug import short_stack print(short_stack()) else: show_help("Don't know what you mean by %r" % info) @@ -795,6 +831,7 @@ Commands: annotate Annotate source files with execution information. combine Combine a number of data files. + debug Display information about the internals of coverage.py erase Erase previously collected coverage data. help Get help on using coverage.py. html Create an HTML report. @@ -807,7 +844,7 @@ """, 'minimum_help': """\ - Code coverage for Python. Use '{program_name} help' for help. + Code coverage for Python, version {__version__} {extension_modifier}. Use '{program_name} help' for help. """, 'version': """\ @@ -858,8 +895,8 @@ def main(argv=None): # pylint: disable=function-redefined """A wrapper around main that profiles.""" + profiler = SimpleLauncher.launch() try: - profiler = SimpleLauncher.launch() return original_main(argv) finally: data, _ = profiler.query(re_filter='coverage', max_records=100)
--- a/eric6/DebugClients/Python/coverage/collector.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/collector.py Thu Sep 17 19:10:36 2020 +0200 @@ -196,6 +196,8 @@ # handle them. self.file_tracers = {} + self.disabled_plugins = set() + # The .should_trace_cache attribute is a cache from file names to # coverage.FileDisposition objects, or None. When a file is first # considered for tracing, a FileDisposition is obtained from @@ -256,6 +258,8 @@ if hasattr(tracer, 'should_start_context'): tracer.should_start_context = self.should_start_context tracer.switch_context = self.switch_context + if hasattr(tracer, 'disable_plugin'): + tracer.disable_plugin = self.disable_plugin fn = tracer.start() self.tracers.append(tracer) @@ -381,6 +385,15 @@ context = new_context self.covdata.set_context(context) + def disable_plugin(self, disposition): + """Disable the plugin mentioned in `disposition`.""" + file_tracer = disposition.file_tracer + plugin = file_tracer._coverage_plugin + plugin_name = plugin._coverage_plugin_name + self.warn("Disabling plug-in {!r} due to previous exception".format(plugin_name)) + plugin._coverage_enabled = False + disposition.trace = False + def cached_mapped_file(self, filename): """A locally cached version of file names mapped through file_mapper.""" key = (type(filename), filename) @@ -408,6 +421,10 @@ return dict((self.cached_mapped_file(k), v) for k, v in items if v) + def plugin_was_disabled(self, plugin): + """Record that `plugin` was disabled during the run.""" + self.disabled_plugins.add(plugin._coverage_plugin_name) + def flush_data(self): """Save the collected data to our associated `CoverageData`. @@ -423,7 +440,12 @@ self.covdata.add_arcs(self.mapped_file_dict(self.data)) else: self.covdata.add_lines(self.mapped_file_dict(self.data)) - self.covdata.add_file_tracers(self.mapped_file_dict(self.file_tracers)) + + file_tracers = { + k: v for k, v in self.file_tracers.items() + if v not in self.disabled_plugins + } + self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers)) self._clear_data() return True
--- a/eric6/DebugClients/Python/coverage/config.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/config.py Thu Sep 17 19:10:36 2020 +0200 @@ -72,7 +72,7 @@ d[opt] = self.get(section, opt) return d - def get(self, section, option, *args, **kwargs): # pylint: disable=arguments-differ + def get(self, section, option, *args, **kwargs): """Get a value, replacing environment variables also. The arguments are the same as `RawConfigParser.get`, but in the found @@ -195,6 +195,7 @@ self.run_include = None self.run_omit = None self.source = None + self.source_pkgs = [] self.timid = False self._crash = None @@ -211,6 +212,7 @@ self.show_missing = False self.skip_covered = False self.skip_empty = False + self.sort = None # Defaults for [html] self.extra_css = None @@ -325,7 +327,7 @@ if used: self.config_file = os.path.abspath(filename) - with open(filename) as f: + with open(filename, "rb") as f: self._config_contents = f.read() return used @@ -360,6 +362,7 @@ ('run_include', 'run:include', 'list'), ('run_omit', 'run:omit', 'list'), ('source', 'run:source', 'list'), + ('source_pkgs', 'run:source_pkgs', 'list'), ('timid', 'run:timid', 'boolean'), ('_crash', 'run:_crash'), @@ -421,6 +424,10 @@ `value` is the new value for the option. """ + # Special-cased options. + if option_name == "paths": + self.paths = value + return # Check all the hard-coded options. for option_spec in self.CONFIG_FILE_OPTIONS: @@ -448,6 +455,10 @@ Returns the value of the option. """ + # Special-cased options. + if option_name == "paths": + return self.paths + # Check all the hard-coded options. for option_spec in self.CONFIG_FILE_OPTIONS: attr, where = option_spec[:2]
--- a/eric6/DebugClients/Python/coverage/control.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/control.py Thu Sep 17 19:10:36 2020 +0200 @@ -99,9 +99,9 @@ def __init__( self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, - source=None, omit=None, include=None, debug=None, + source=None, source_pkgs=None, omit=None, include=None, debug=None, concurrency=None, check_preimported=False, context=None, - ): + ): # pylint: disable=too-many-arguments """ Many of these arguments duplicate and override values that can be provided in a configuration file. Parameters that are missing here @@ -146,6 +146,10 @@ in the trees indicated by the file paths or package names will be measured. + `source_pkgs` is a list of package names. It works the same as + `source`, but can be used to name packages where the name can also be + interpreted as a file path. + `include` and `omit` are lists of file name patterns. Files that match `include` will be measured, files that match `omit` will not. Each will also accept a single string argument. @@ -176,6 +180,9 @@ .. versionadded:: 5.0 The `check_preimported` and `context` parameters. + .. versionadded:: 5.3 + The `source_pkgs` parameter. + """ # data_file=None means no disk file at all. data_file missing means # use the value from the config file. @@ -188,7 +195,7 @@ config_file=config_file, data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), - source=source, run_omit=omit, run_include=include, debug=debug, + source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug, report_omit=omit, report_include=include, concurrency=concurrency, context=context, ) @@ -212,7 +219,6 @@ self._data = self._collector = None self._plugins = None self._inorout = None - self._inorout_class = InOrOut self._data_suffix = self._run_suffix = None self._exclude_re = None self._debug = None @@ -367,7 +373,11 @@ option name. For example, the ``branch`` option in the ``[run]`` section of the config file would be indicated with `"run:branch"`. - Returns the value of the option. + Returns the value of the option. The type depends on the option + selected. + + As a special case, an `option_name` of ``"paths"`` will return an + OrderedDict with the entire ``[paths]`` section value. .. versionadded:: 4.0 @@ -394,6 +404,9 @@ [run] branch = True + As a special case, an `option_name` of ``"paths"`` will replace the + entire ``[paths]`` section. The value should be an OrderedDict. + .. versionadded:: 4.0 """ @@ -476,7 +489,10 @@ plugin._coverage_enabled = False # Create the file classifying substructure. - self._inorout = self._inorout_class(warn=self._warn) + self._inorout = InOrOut( + warn=self._warn, + debug=(self._debug if self._debug.should('trace') else None), + ) self._inorout.configure(self.config) self._inorout.plugins = self._plugins self._inorout.disp_class = self._collector.file_disposition_class @@ -545,7 +561,7 @@ def _atexit(self): """Clean up on process shutdown.""" if self._debug.should("process"): - self._debug.write("atexit: {!r}".format(self)) + self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self)) if self._started: self.stop() if self._auto_save: @@ -693,6 +709,10 @@ self._init_data(suffix=None) self._post_init() + for plugin in self._plugins: + if not plugin._coverage_enabled: + self._collector.plugin_was_disabled(plugin) + if self._collector and self._collector.flush_data(): self._post_save_work() @@ -822,7 +842,7 @@ def report( self, morfs=None, show_missing=None, ignore_errors=None, file=None, omit=None, include=None, skip_covered=None, - contexts=None, skip_empty=None, + contexts=None, skip_empty=None, precision=None, sort=None ): """Write a textual summary report to `file`. @@ -850,6 +870,9 @@ expressions (using :func:`re.search <python:re.search>`) will be included in the report. + `precision` is the number of digits to display after the decimal + point for percentages. + All of the arguments default to the settings read from the :ref:`configuration file <config>`. @@ -861,12 +884,16 @@ .. versionadded:: 5.0 The `contexts` and `skip_empty` parameters. + .. versionadded:: 5.2 + The `precision` parameter. + """ with override_config( self, ignore_errors=ignore_errors, report_omit=omit, report_include=include, show_missing=show_missing, skip_covered=skip_covered, - report_contexts=contexts, skip_empty=skip_empty, + report_contexts=contexts, skip_empty=skip_empty, precision=precision, + sort=sort ): reporter = SummaryReporter(self) return reporter.report(morfs, outfile=file) @@ -892,10 +919,12 @@ reporter = AnnotateReporter(self) reporter.report(morfs, directory=directory) - def html_report(self, morfs=None, directory=None, ignore_errors=None, - omit=None, include=None, extra_css=None, title=None, - skip_covered=None, show_contexts=None, contexts=None, - skip_empty=None): + def html_report( + self, morfs=None, directory=None, ignore_errors=None, + omit=None, include=None, extra_css=None, title=None, + skip_covered=None, show_contexts=None, contexts=None, + skip_empty=None, precision=None, + ): """Generate an HTML report. The HTML is written to `directory`. The file "index.html" is the @@ -923,14 +952,14 @@ ignore_errors=ignore_errors, report_omit=omit, report_include=include, html_dir=directory, extra_css=extra_css, html_title=title, skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts, - skip_empty=skip_empty, + skip_empty=skip_empty, precision=precision, ): reporter = HtmlReporter(self) return reporter.report(morfs) def xml_report( self, morfs=None, outfile=None, ignore_errors=None, - omit=None, include=None, contexts=None, + omit=None, include=None, contexts=None, skip_empty=None, ): """Generate an XML report of coverage results. @@ -946,7 +975,7 @@ """ with override_config(self, ignore_errors=ignore_errors, report_omit=omit, report_include=include, - xml_output=outfile, report_contexts=contexts, + xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty, ): return render_report(self.config.xml_output, XmlReporter(self), morfs) @@ -1077,8 +1106,7 @@ # flag (an attribute on this function) to indicate that coverage.py has # already been started, so we can avoid doing it twice. # - # https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy has more - # details. + # https://github.com/nedbat/coveragepy/issues/340 has more details. if hasattr(process_startup, "coverage"): # We've annotated this function before, so we must have already
--- a/eric6/DebugClients/Python/coverage/env.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/env.py Thu Sep 17 19:10:36 2020 +0200 @@ -57,7 +57,7 @@ unpackings_pep448 = (PYVERSION >= (3, 5)) # Can co_lnotab have negative deltas? - negative_lnotab = (PYVERSION >= (3, 6)) + negative_lnotab = (PYVERSION >= (3, 6)) and not (PYPY and PYPYVERSION < (7, 2)) # Do .pyc files conform to PEP 552? Hash-based pyc's. hashed_pyc_pep552 = (PYVERSION >= (3, 7, 0, 'alpha', 4)) @@ -85,10 +85,6 @@ # Python 3.9a1 made sys.argv[0] and other reported files absolute paths. report_absolute_files = (PYVERSION >= (3, 9)) - # Python 3.9a2 changed how return/finally was traced, but it was - # temporary. - bpo39114 = (PYVERSION == (3, 9, 0, 'alpha', 2, 0)) - # Coverage.py specifics. # Are we using the C-implemented trace function?
--- a/eric6/DebugClients/Python/coverage/html.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/html.py Thu Sep 17 19:10:36 2020 +0200 @@ -11,7 +11,7 @@ import coverage from coverage import env -from coverage.backward import iitems, SimpleNamespace +from coverage.backward import iitems, SimpleNamespace, format_local_datetime from coverage.data import add_data_to_hash from coverage.files import flat_rootname from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module @@ -200,7 +200,7 @@ '__url__': coverage.__url__, '__version__': coverage.__version__, 'title': title, - 'time_stamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), + 'time_stamp': format_local_datetime(datetime.datetime.now()), 'extra_css': self.extra_css, 'has_arcs': self.has_arcs, 'show_contexts': self.config.show_contexts,
--- a/eric6/DebugClients/Python/coverage/inorout.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/inorout.py Thu Sep 17 19:10:36 2020 +0200 @@ -111,8 +111,9 @@ class InOrOut(object): """Machinery for determining what files to measure.""" - def __init__(self, warn): + def __init__(self, warn, debug): self.warn = warn + self.debug = debug # The matchers for should_trace. self.source_match = None @@ -131,6 +132,7 @@ def configure(self, config): """Apply the configuration to get ready for decision-time.""" + self.source_pkgs.extend(config.source_pkgs) for src in config.source or []: if os.path.isdir(src): self.source.append(canonical_filename(src)) @@ -177,19 +179,33 @@ for mod in [contracts, six]: self.cover_paths.append(canonical_path(mod)) + def debug(msg): + if self.debug: + self.debug.write(msg) + # Create the matchers we need for should_trace if self.source or self.source_pkgs: - self.source_match = TreeMatcher(self.source) - self.source_pkgs_match = ModuleMatcher(self.source_pkgs) + against = [] + if self.source: + self.source_match = TreeMatcher(self.source) + against.append("trees {!r}".format(self.source_match)) + if self.source_pkgs: + self.source_pkgs_match = ModuleMatcher(self.source_pkgs) + against.append("modules {!r}".format(self.source_pkgs_match)) + debug("Source matching against " + " and ".join(against)) else: if self.cover_paths: self.cover_match = TreeMatcher(self.cover_paths) + debug("Coverage code matching: {!r}".format(self.cover_match)) if self.pylib_paths: self.pylib_match = TreeMatcher(self.pylib_paths) + debug("Python stdlib matching: {!r}".format(self.pylib_match)) if self.include: self.include_match = FnmatchMatcher(self.include) + debug("Include matching: {!r}".format(self.include_match)) if self.omit: self.omit_match = FnmatchMatcher(self.omit) + debug("Omit matching: {!r}".format(self.omit_match)) def should_trace(self, filename, frame=None): """Decide whether to trace execution in `filename`, with a reason. @@ -309,12 +325,21 @@ # about the outer bound of what to measure and we don't have to apply # any canned exclusions. If they didn't, then we have to exclude the # stdlib and coverage.py directories. - if self.source_match: - if self.source_pkgs_match.match(modulename): - if modulename in self.source_pkgs_unmatched: - self.source_pkgs_unmatched.remove(modulename) - elif not self.source_match.match(filename): - return "falls outside the --source trees" + if self.source_match or self.source_pkgs_match: + extra = "" + ok = False + if self.source_pkgs_match: + if self.source_pkgs_match.match(modulename): + ok = True + if modulename in self.source_pkgs_unmatched: + self.source_pkgs_unmatched.remove(modulename) + else: + extra = "module {!r} ".format(modulename) + if not ok and self.source_match: + if self.source_match.match(filename): + ok = True + if not ok: + return extra + "falls outside the --source spec" elif self.include_match: if not self.include_match.match(filename): return "falls outside the --include trees"
--- a/eric6/DebugClients/Python/coverage/jsonreport.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/jsonreport.py Thu Sep 17 19:10:36 2020 +0200 @@ -60,6 +60,8 @@ self.report_data["totals"].update({ 'num_branches': self.total.n_branches, 'num_partial_branches': self.total.n_partial_branches, + 'covered_branches': self.total.n_executed_branches, + 'missing_branches': self.total.n_missing_branches, }) json.dump( @@ -95,5 +97,7 @@ reported_file['summary'].update({ 'num_branches': nums.n_branches, 'num_partial_branches': nums.n_partial_branches, + 'covered_branches': nums.n_executed_branches, + 'missing_branches': nums.n_missing_branches, }) return reported_file
--- a/eric6/DebugClients/Python/coverage/multiproc.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/multiproc.py Thu Sep 17 19:10:36 2020 +0200 @@ -28,7 +28,7 @@ class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method """A replacement for multiprocess.Process that starts coverage.""" - def _bootstrap(self, *args, **kwargs): # pylint: disable=arguments-differ + def _bootstrap(self, *args, **kwargs): # pylint: disable=signature-differs """Wrapper around _bootstrap to start coverage.""" try: from coverage import Coverage # avoid circular import
--- a/eric6/DebugClients/Python/coverage/parser.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/parser.py Thu Sep 17 19:10:36 2020 +0200 @@ -610,6 +610,7 @@ return node.lineno _line__FunctionDef = _line_decorated + _line__AsyncFunctionDef = _line_decorated def _line__List(self, node): if node.elts:
--- a/eric6/DebugClients/Python/coverage/plugin.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/plugin.py Thu Sep 17 19:10:36 2020 +0200 @@ -496,6 +496,7 @@ * ``'num'``: a number * ``'op'``: an operator * ``'str'``: a string literal + * ``'ws'``: some white space * ``'txt'``: some other kind of text If you concatenate all the token texts, and then join them with
--- a/eric6/DebugClients/Python/coverage/sqldata.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/sqldata.py Thu Sep 17 19:10:36 2020 +0200 @@ -268,7 +268,7 @@ """Read the metadata from a database so that we are ready to use it.""" with self._dbs[get_thread_id()] as db: try: - schema_version, = db.execute("select version from coverage_schema").fetchone() + schema_version, = db.execute_one("select version from coverage_schema") except Exception as exc: raise CoverageException( "Data file {!r} doesn't seem to be a coverage data file: {}".format( @@ -374,7 +374,7 @@ assert context is not None self._start_using() with self._connect() as con: - row = con.execute("select id from context where context = ?", (context,)).fetchone() + row = con.execute_one("select id from context where context = ?", (context,)) if row is not None: return row[0] else: @@ -789,7 +789,7 @@ file_id = self._file_id(filename) if file_id is None: return None - row = con.execute("select tracer from tracer where file_id = ?", (file_id,)).fetchone() + row = con.execute_one("select tracer from tracer where file_id = ?", (file_id,)) if row is not None: return row[0] or "" return "" # File was measured, but no tracer associated. @@ -952,11 +952,13 @@ """ with SqliteDb(":memory:", debug=NoDebugging()) as db: + temp_store = [row[0] for row in db.execute("pragma temp_store")] compile_options = [row[0] for row in db.execute("pragma compile_options")] return [ ('sqlite3_version', sqlite3.version), ('sqlite3_sqlite_version', sqlite3.sqlite_version), + ('sqlite3_temp_store', temp_store), ('sqlite3_compile_options', compile_options), ] @@ -1062,6 +1064,23 @@ self.debug.write("EXCEPTION from execute: {}".format(msg)) raise CoverageException("Couldn't use data file {!r}: {}".format(self.filename, msg)) + def execute_one(self, sql, parameters=()): + """Execute a statement and return the one row that results. + + This is like execute(sql, parameters).fetchone(), except it is + correct in reading the entire result set. This will raise an + exception if more than one row results. + + Returns a row, or None if there were no rows. + """ + rows = list(self.execute(sql, parameters)) + if len(rows) == 0: + return None + elif len(rows) == 1: + return rows[0] + else: + raise CoverageException("Sql {!r} shouldn't return {} rows".format(sql, len(rows))) + def executemany(self, sql, data): """Same as :meth:`python:sqlite3.Connection.executemany`.""" if self.debug:
--- a/eric6/DebugClients/Python/coverage/summary.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/summary.py Thu Sep 17 19:10:36 2020 +0200 @@ -104,10 +104,18 @@ # Sort the lines and write them out. if getattr(self.config, 'sort', None): - position = column_order.get(self.config.sort.lower()) + sort_option = self.config.sort.lower() + reverse = False + if sort_option[0] == '-': + reverse = True + sort_option = sort_option[1:] + elif sort_option[0] == '+': + sort_option = sort_option[1:] + + position = column_order.get(sort_option) if position is None: raise CoverageException("Invalid sorting option: {!r}".format(self.config.sort)) - lines.sort(key=lambda l: (l[1][position], l[0])) + lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse) for line in lines: self.writeout(line[0])
--- a/eric6/DebugClients/Python/coverage/version.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/version.py Thu Sep 17 19:10:36 2020 +0200 @@ -5,7 +5,7 @@ # This file is exec'ed in setup.py, don't import anything! # Same semantics as sys.version_info. -version_info = (5, 0, 3, 'final', 0) +version_info = (5, 3, 0, "final", 0) def _make_version(major, minor, micro, releaselevel, serial):
--- a/eric6/DebugClients/Python/coverage/xmlreport.py Tue Sep 15 19:09:05 2020 +0200 +++ b/eric6/DebugClients/Python/coverage/xmlreport.py Thu Sep 17 19:10:36 2020 +0200 @@ -41,7 +41,9 @@ if self.config.source: for src in self.config.source: if os.path.exists(src): - self.source_paths.add(files.canonical_filename(src)) + if not self.config.relative_files: + src = files.canonical_filename(src) + self.source_paths.add(src) self.packages = {} self.xml_out = None @@ -140,22 +142,26 @@ def xml_file(self, fr, analysis, has_arcs): """Add to the XML report for a single file.""" + if self.config.skip_empty: + if analysis.numbers.n_statements == 0: + return + # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. filename = fr.filename.replace("\\", "/") for source_path in self.source_paths: + source_path = files.canonical_filename(source_path) if filename.startswith(source_path.replace("\\", "/") + "/"): rel_name = filename[len(source_path)+1:] break else: rel_name = fr.relative_filename() + self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/")) dirname = os.path.dirname(rel_name) or u"." dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth]) package_name = dirname.replace("/", ".") - if rel_name != fr.filename: - self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/")) package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class")