Sat, 21 Aug 2021 14:21:44 +0200
Upgraded the included code coverage library to v5.5.0.
--- a/docs/changelog Fri Aug 20 19:56:17 2021 +0200 +++ b/docs/changelog Sat Aug 21 14:21:44 2021 +0200 @@ -14,6 +14,8 @@ dialog - Shell -- added capability to save the contents of the shell window into a file +- Third Party packages + -- upgraded coverage to 5.5.0 Version 21.9: - bug fixes
--- a/eric7.epj Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7.epj Sat Aug 21 14:21:44 2021 +0200 @@ -1005,7 +1005,6 @@ "eric7/DebugClients/Python/coverage/__init__.py", "eric7/DebugClients/Python/coverage/__main__.py", "eric7/DebugClients/Python/coverage/annotate.py", - "eric7/DebugClients/Python/coverage/backunittest.py", "eric7/DebugClients/Python/coverage/backward.py", "eric7/DebugClients/Python/coverage/bytecode.py", "eric7/DebugClients/Python/coverage/cmdline.py", @@ -1025,7 +1024,6 @@ "eric7/DebugClients/Python/coverage/misc.py", "eric7/DebugClients/Python/coverage/multiproc.py", "eric7/DebugClients/Python/coverage/numbits.py", - "eric7/DebugClients/Python/coverage/optional.py", "eric7/DebugClients/Python/coverage/parser.py", "eric7/DebugClients/Python/coverage/phystokens.py", "eric7/DebugClients/Python/coverage/plugin.py",
--- a/eric7/DebugClients/Python/coverage/backunittest.py Fri Aug 20 19:56:17 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -"""Implementations of unittest features from the future.""" - -import unittest - - -def unittest_has(method): - """Does `unittest.TestCase` have `method` defined?""" - return hasattr(unittest.TestCase, method) - - -class TestCase(unittest.TestCase): - """Just like unittest.TestCase, but with assert methods added. - - Designed to be compatible with 3.1 unittest. Methods are only defined if - `unittest` doesn't have them. - - """ - # pylint: disable=signature-differs, deprecated-method - - if not unittest_has('assertCountEqual'): - def assertCountEqual(self, *args, **kwargs): - return self.assertItemsEqual(*args, **kwargs) - - if not unittest_has('assertRaisesRegex'): - def assertRaisesRegex(self, *args, **kwargs): - return self.assertRaisesRegexp(*args, **kwargs) - - if not unittest_has('assertRegex'): - def assertRegex(self, *args, **kwargs): - return self.assertRegexpMatches(*args, **kwargs)
--- a/eric7/DebugClients/Python/coverage/backward.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/backward.py Sat Aug 21 14:21:44 2021 +0200 @@ -78,7 +78,9 @@ try: import reprlib -except ImportError: +except ImportError: # pragma: not covered + # We need this on Python 2, but in testing environments, a backport is + # installed, so this import isn't used. import repr as reprlib # A function to iterate listlessly over a dict's items, and one to get the @@ -215,9 +217,6 @@ items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) return "{}({})".format(type(self).__name__, ", ".join(items)) - def __eq__(self, other): - return self.__dict__ == other.__dict__ - def format_local_datetime(dt): """Return a string with local timezone representing the date. @@ -245,15 +244,17 @@ """ try: - from importlib.machinery import SourceFileLoader + import importlib.util as importlib_util except ImportError: - SourceFileLoader = None + importlib_util = None if modfile is None: modfile = modname + '.py' - if SourceFileLoader: - # pylint: disable=no-value-for-parameter, deprecated-method - mod = SourceFileLoader(modname, modfile).load_module() + if importlib_util: + spec = importlib_util.spec_from_file_location(modname, modfile) + mod = importlib_util.module_from_spec(spec) + sys.modules[modname] = mod + spec.loader.exec_module(mod) else: for suff in imp.get_suffixes(): # pragma: part covered if suff[0] == '.py':
--- a/eric7/DebugClients/Python/coverage/cmdline.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/cmdline.py Sat Aug 21 14:21:44 2021 +0200 @@ -31,6 +31,10 @@ '-a', '--append', action='store_true', help="Append coverage data to .coverage, otherwise it starts clean each time.", ) + keep = optparse.make_option( + '', '--keep', action='store_true', + help="Keep original coverage files, otherwise they are deleted.", + ) branch = optparse.make_option( '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage.", @@ -215,6 +219,7 @@ help=None, ignore_errors=None, include=None, + keep=None, module=None, omit=None, contexts=None, @@ -333,6 +338,7 @@ "combine", [ Opts.append, + Opts.keep, ] + GLOBAL_ARGS, usage="[options] <path1> <path2> ... <pathN>", description=( @@ -585,7 +591,7 @@ if options.append: self.coverage.load() data_dirs = args or None - self.coverage.combine(data_dirs, strict=True) + self.coverage.combine(data_dirs, strict=True, keep=bool(options.keep)) self.coverage.save() return OK @@ -765,7 +771,7 @@ self.coverage.load() data = self.coverage.get_data() print(info_header("data")) - print("path: %s" % self.coverage.get_data().data_filename()) + print("path: %s" % data.data_filename()) if data: print("has_arcs: %r" % data.has_arcs()) summary = line_counts(data, fullpath=True)
--- a/eric7/DebugClients/Python/coverage/collector.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/collector.py Sat Aug 21 14:21:44 2021 +0200 @@ -55,7 +55,7 @@ _collectors = [] # The concurrency settings we support here. - SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"]) + SUPPORTED_CONCURRENCIES = {"greenlet", "eventlet", "gevent", "thread"} def __init__( self, should_trace, check_include, should_start_context, file_mapper,
--- a/eric7/DebugClients/Python/coverage/config.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/config.py Sat Aug 21 14:21:44 2021 +0200 @@ -63,7 +63,7 @@ real_section = section_prefix + section if configparser.RawConfigParser.has_section(self, real_section): return configparser.RawConfigParser.options(self, real_section) - raise configparser.NoSectionError + raise configparser.NoSectionError(section) def get_section(self, section): """Get the contents of a section, as a dictionary.""" @@ -87,7 +87,7 @@ if configparser.RawConfigParser.has_option(self, real_section, option): break else: - raise configparser.NoOptionError + raise configparser.NoOptionError(option, section) v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) v = substitute_variables(v, os.environ) @@ -217,6 +217,8 @@ # Defaults for [html] self.extra_css = None self.html_dir = "htmlcov" + self.html_skip_covered = None + self.html_skip_empty = None self.html_title = "Coverage report" self.show_contexts = False @@ -384,6 +386,8 @@ # [html] ('extra_css', 'html:extra_css'), ('html_dir', 'html:directory'), + ('html_skip_covered', 'html:skip_covered', 'boolean'), + ('html_skip_empty', 'html:skip_empty', 'boolean'), ('html_title', 'html:title'), ('show_contexts', 'html:show_contexts', 'boolean'), @@ -473,6 +477,20 @@ # If we get here, we didn't find the option. raise CoverageException("No such option: %r" % option_name) + def post_process_file(self, path): + """Make final adjustments to a file path to make it usable.""" + return os.path.expanduser(path) + + def post_process(self): + """Make final adjustments to settings to make them usable.""" + self.data_file = self.post_process_file(self.data_file) + self.html_dir = self.post_process_file(self.html_dir) + self.xml_output = self.post_process_file(self.xml_output) + self.paths = collections.OrderedDict( + (k, [self.post_process_file(f) for f in v]) + for k, v in self.paths.items() + ) + def config_files_to_try(config_file): """What config files should we try to read? @@ -547,12 +565,6 @@ # Once all the config has been collected, there's a little post-processing # to do. - config.data_file = os.path.expanduser(config.data_file) - config.html_dir = os.path.expanduser(config.html_dir) - config.xml_output = os.path.expanduser(config.xml_output) - config.paths = collections.OrderedDict( - (k, [os.path.expanduser(f) for f in v]) - for k, v in config.paths.items() - ) + config.post_process() return config
--- a/eric7/DebugClients/Python/coverage/control.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/control.py Sat Aug 21 14:21:44 2021 +0200 @@ -659,7 +659,7 @@ data = self.get_data() data.write() - def combine(self, data_paths=None, strict=False): + def combine(self, data_paths=None, strict=False, keep=False): """Combine together a number of similarly-named coverage data files. All coverage data files whose name starts with `data_file` (from the @@ -674,12 +674,16 @@ If `strict` is true, then it is an error to attempt to combine when there are no data files to combine. + If `keep` is true, then original input data files won't be deleted. + .. versionadded:: 4.0 The `data_paths` parameter. .. versionadded:: 4.3 The `strict` parameter. + .. versionadded: 5.5 + The `keep` parameter. """ self._init() self._init_data(suffix=None) @@ -694,7 +698,13 @@ for pattern in paths[1:]: aliases.add(pattern, result) - combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict) + combine_parallel_data( + self._data, + aliases=aliases, + data_paths=data_paths, + strict=strict, + keep=keep, + ) def get_data(self): """Get the collected data. @@ -955,8 +965,8 @@ with override_config(self, 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, precision=precision, + html_skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts, + html_skip_empty=skip_empty, precision=precision, ): reporter = HtmlReporter(self) return reporter.report(morfs)
--- a/eric7/DebugClients/Python/coverage/data.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/data.py Sat Aug 21 14:21:44 2021 +0200 @@ -52,7 +52,7 @@ hasher.update(data.file_tracer(filename)) -def combine_parallel_data(data, aliases=None, data_paths=None, strict=False): +def combine_parallel_data(data, aliases=None, data_paths=None, strict=False, keep=False): """Combine a number of data files together. Treat `data.filename` as a file prefix, and combine the data from all @@ -68,7 +68,7 @@ If `data_paths` is not provided, then the directory portion of `data.filename` is used as the directory to search for data files. - Every data file found and combined is then deleted from disk. If a file + Unless `keep` is True every data file found and combined is then deleted from disk. If a file cannot be read, a warning will be issued, and the file will not be deleted. @@ -116,9 +116,10 @@ else: data.update(new_data, aliases=aliases) files_combined += 1 - if data._debug.should('dataio'): - data._debug.write("Deleting combined data file %r" % (f,)) - file_be_gone(f) + if not keep: + if data._debug.should('dataio'): + data._debug.write("Deleting combined data file %r" % (f,)) + file_be_gone(f) if strict and not files_combined: raise CoverageException("No usable data files")
--- a/eric7/DebugClients/Python/coverage/doc/CHANGES.rst Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/doc/CHANGES.rst Sat Aug 21 14:21:44 2021 +0200 @@ -21,6 +21,77 @@ .. Version 9.8.1 --- 2027-07-27 .. ---------------------------- +.. _changes_55: + +Version 5.5 --- 2021-02-28 +-------------------------- + +- ``coverage combine`` has a new option, ``--keep`` to keep the original data + files after combining them. The default is still to delete the files after + they have been combined. This was requested in `issue 1108`_ and implemented + in `pull request 1110`_. Thanks, Éric Larivière. + +- When reporting missing branches in ``coverage report``, branches aren't + reported that jump to missing lines. This adds to the long-standing behavior + of not reporting branches from missing lines. Now branches are only reported + if both the source and destination lines are executed. Closes both `issue + 1065`_ and `issue 955`_. + +- Minor improvements to the HTML report: + + - The state of the line visibility selector buttons is saved in local storage + so you don't have to fiddle with them so often, fixing `issue 1123`_. + + - It has a little more room for line numbers so that 4-digit numbers work + well, fixing `issue 1124`_. + +- Improved the error message when combining line and branch data, so that users + will be more likely to understand what's happening, closing `issue 803`_. + +.. _issue 803: https://github.com/nedbat/coveragepy/issues/803 +.. _issue 955: https://github.com/nedbat/coveragepy/issues/955 +.. _issue 1065: https://github.com/nedbat/coveragepy/issues/1065 +.. _issue 1108: https://github.com/nedbat/coveragepy/issues/1108 +.. _pull request 1110: https://github.com/nedbat/coveragepy/pull/1110 +.. _issue 1123: https://github.com/nedbat/coveragepy/issues/1123 +.. _issue 1124: https://github.com/nedbat/coveragepy/issues/1124 + + +.. _changes_54: + +Version 5.4 --- 2021-01-24 +-------------------------- + +- The text report produced by ``coverage report`` now always outputs a TOTAL + line, even if only one Python file is reported. This makes regex parsing + of the output easier. Thanks, Judson Neer. This had been requested a number + of times (`issue 1086`_, `issue 922`_, `issue 732`_). + +- The ``skip_covered`` and ``skip_empty`` settings in the configuration file + can now be specified in the ``[html]`` section, so that text reports and HTML + reports can use separate settings. The HTML report will still use the + ``[report]`` settings if there isn't a value in the ``[html]`` section. + Closes `issue 1090`_. + +- Combining files on Windows across drives now works properly, fixing `issue + 577`_. Thanks, `Valentin Lab <pr1080_>`_. + +- Fix an obscure warning from deep in the _decimal module, as reported in + `issue 1084`_. + +- Update to support Python 3.10 alphas in progress, including `PEP 626: Precise + line numbers for debugging and other tools <pep626_>`_. + +.. _issue 577: https://github.com/nedbat/coveragepy/issues/577 +.. _issue 732: https://github.com/nedbat/coveragepy/issues/732 +.. _issue 922: https://github.com/nedbat/coveragepy/issues/922 +.. _issue 1084: https://github.com/nedbat/coveragepy/issues/1084 +.. _issue 1086: https://github.com/nedbat/coveragepy/issues/1086 +.. _issue 1090: https://github.com/nedbat/coveragepy/issues/1090 +.. _pr1080: https://github.com/nedbat/coveragepy/pull/1080 +.. _pep626: https://www.python.org/dev/peps/pep-0626/ + + .. _changes_531: Version 5.3.1 --- 2020-12-19
--- a/eric7/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt Sat Aug 21 14:21:44 2021 +0200 @@ -19,6 +19,7 @@ Arcadiy Ivanov Aron Griffis Artem Dayneko +Arthur Deygin Ben Finney Bernát Gábor Bill Hart @@ -54,9 +55,10 @@ Dmitry Shishov Dmitry Trofimov Eduardo Schettino +Edward Loper Eli Skeggs Emil Madsen -Edward Loper +Éric Larivière Federico Bond Frazer McLean Geoff Bache @@ -79,6 +81,7 @@ Jon Dufresne Joseph Tate Josh Williams +Judson Neer Julian Berman Julien Voisin Justas Sadzevičius @@ -135,6 +138,7 @@ Thijs Triemstra Thomas Grainger Titus Brown +Valentin Lab Vince Salvino Ville Skyttä Xie Yanbo
--- a/eric7/DebugClients/Python/coverage/doc/README.rst Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/doc/README.rst Sat Aug 21 14:21:44 2021 +0200 @@ -8,7 +8,7 @@ Code coverage testing for Python. | |license| |versions| |status| -| |ci-status| |docs| |codecov| +| |test-status| |quality-status| |docs| |codecov| | |kit| |format| |repos| |downloads| | |stars| |forks| |contributors| | |tidelift| |twitter-coveragepy| |twitter-nedbat| @@ -21,7 +21,7 @@ * CPython 2.7. * CPython 3.5 through 3.10 alpha. -* PyPy2 7.3.1 and PyPy3 7.3.1. +* PyPy2 7.3.3 and PyPy3 7.3.3. Documentation is on `Read the Docs`_. Code repository and issue tracker are on `GitHub`_. @@ -95,9 +95,12 @@ .. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt -.. |ci-status| image:: https://github.com/nedbat/coveragepy/workflows/Test%20Suite/badge.svg - :target: https://github.com/nedbat/coveragepy/actions?query=workflow%3A%22Test+Suite%22 - :alt: Build status +.. |test-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml/badge.svg?branch=master&event=push + :target: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml + :alt: Test suite status +.. |quality-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml/badge.svg?branch=master&event=push + :target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml + :alt: Quality check status .. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat :target: https://coverage.readthedocs.io/ :alt: Documentation
--- a/eric7/DebugClients/Python/coverage/env.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/env.py Sat Aug 21 14:21:44 2021 +0200 @@ -11,35 +11,49 @@ WINDOWS = sys.platform == "win32" LINUX = sys.platform.startswith("linux") +# Python implementations. +CPYTHON = (platform.python_implementation() == "CPython") +PYPY = (platform.python_implementation() == "PyPy") +JYTHON = (platform.python_implementation() == "Jython") +IRONPYTHON = (platform.python_implementation() == "IronPython") + # Python versions. We amend version_info with one more value, a zero if an # official version, or 1 if built from source beyond an official version. PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),) PY2 = PYVERSION < (3, 0) PY3 = PYVERSION >= (3, 0) -# Python implementations. -PYPY = (platform.python_implementation() == 'PyPy') if PYPY: PYPYVERSION = sys.pypy_version_info PYPY2 = PYPY and PY2 PYPY3 = PYPY and PY3 -JYTHON = (platform.python_implementation() == 'Jython') -IRONPYTHON = (platform.python_implementation() == 'IronPython') - -# Python behavior +# Python behavior. class PYBEHAVIOR(object): """Flags indicating this Python's behavior.""" + pep626 = CPYTHON and (PYVERSION > (3, 10, 0, 'alpha', 4)) + # Is "if __debug__" optimized away? - optimize_if_debug = (not PYPY) + if PYPY3: + optimize_if_debug = True + elif PYPY2: + optimize_if_debug = False + else: + optimize_if_debug = not pep626 # Is "if not __debug__" optimized away? optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4)) + if pep626: + optimize_if_not_debug = False + if PYPY3: + optimize_if_not_debug = True # Is "if not __debug__" optimized away even better? optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1)) + if pep626: + optimize_if_not_debug2 = False # Do we have yield-from? yield_from = (PYVERSION >= (3, 3)) @@ -66,13 +80,16 @@ # used to be an empty string (meaning the current directory). It changed # to be the actual path to the current directory, so that os.chdir wouldn't # affect the outcome. - actual_syspath0_dash_m = (not PYPY) and (PYVERSION >= (3, 7, 0, 'beta', 3)) + actual_syspath0_dash_m = CPYTHON and (PYVERSION >= (3, 7, 0, 'beta', 3)) + + # 3.7 changed how functions with only docstrings are numbered. + docstring_only_function = (not PYPY) and ((3, 7, 0, 'beta', 5) <= PYVERSION <= (3, 10)) # When a break/continue/return statement in a try block jumps to a finally # block, does the finally block do the break/continue/return (pre-3.8), or # does the finally jump back to the break/continue/return (3.8) to do the # work? - finally_jumps_back = (PYVERSION >= (3, 8)) + finally_jumps_back = ((3, 8) <= PYVERSION < (3, 10)) # When a function is decorated, does the trace function get called for the # @-line and also the def-line (new behavior in 3.8)? Or just the @-line @@ -85,6 +102,20 @@ # Python 3.9a1 made sys.argv[0] and other reported files absolute paths. report_absolute_files = (PYVERSION >= (3, 9)) + # Lines after break/continue/return/raise are no longer compiled into the + # bytecode. They used to be marked as missing, now they aren't executable. + omit_after_jump = pep626 + + # PyPy has always omitted statements after return. + omit_after_return = omit_after_jump or PYPY + + # Modules used to have firstlineno equal to the line number of the first + # real line of code. Now they always start at 1. + module_firstline_1 = pep626 + + # Are "if 0:" lines (and similar) kept in the compiled code? + keep_constant_test = pep626 + # Coverage.py specifics. # Are we using the C-implemented trace function?
--- a/eric7/DebugClients/Python/coverage/files.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/files.py Sat Aug 21 14:21:44 2021 +0200 @@ -359,17 +359,19 @@ match an entire tree, and not just its root. """ + pattern_sep = sep(pattern) + if len(pattern) > 1: pattern = pattern.rstrip(r"\/") # The pattern can't end with a wildcard component. if pattern.endswith("*"): raise CoverageException("Pattern must not end with wildcards.") - pattern_sep = sep(pattern) # The pattern is meant to match a filepath. Let's make it absolute # unless it already is, or is meant to match any prefix. - if not pattern.startswith('*') and not isabs_anywhere(pattern): + if not pattern.startswith('*') and not isabs_anywhere(pattern + + pattern_sep): pattern = abs_file(pattern) if not pattern.endswith(pattern_sep): pattern += pattern_sep
--- a/eric7/DebugClients/Python/coverage/html.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/html.py Sat Aug 21 14:21:44 2021 +0200 @@ -84,7 +84,7 @@ data = self.coverage.get_data() self.has_arcs = data.has_arcs() if self.config.show_contexts: - if data.measured_contexts() == set([""]): + if data.measured_contexts() == {""}: self.coverage._warn("No contexts were measured") data.set_query_contexts(self.config.report_contexts) @@ -173,6 +173,14 @@ self.coverage = cov self.config = self.coverage.config self.directory = self.config.html_dir + + self.skip_covered = self.config.html_skip_covered + if self.skip_covered is None: + self.skip_covered = self.config.skip_covered + self.skip_empty = self.config.html_skip_empty + if self.skip_empty is None: + self.skip_empty= self.config.skip_empty + title = self.config.html_title if env.PY2: title = title.decode("utf8") @@ -271,7 +279,7 @@ nums = analysis.numbers self.all_files_nums.append(nums) - if self.config.skip_covered: + if self.skip_covered: # Don't report on 100% files. no_missing_lines = (nums.n_missing == 0) no_missing_branches = (nums.n_partial_branches == 0) @@ -280,7 +288,7 @@ file_be_gone(html_path) return - if self.config.skip_empty: + if self.skip_empty: # Don't report on empty files. if nums.n_statements == 0: file_be_gone(html_path)
--- a/eric7/DebugClients/Python/coverage/misc.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/misc.py Sat Aug 21 14:21:44 2021 +0200 @@ -63,7 +63,7 @@ def new_contract(*args, **kwargs): """A proxy for contracts.new_contract that doesn't mind happening twice.""" try: - return raw_new_contract(*args, **kwargs) + raw_new_contract(*args, **kwargs) except ValueError: # During meta-coverage, this module is imported twice, and # PyContracts doesn't like redefining contracts. It's OK. @@ -77,7 +77,7 @@ def one_of(argnames): """Ensure that only one of the argnames is non-None.""" def _decorator(func): - argnameset = set(name.strip() for name in argnames.split(",")) + argnameset = {name.strip() for name in argnames.split(",")} def _wrapper(*args, **kwargs): vals = [kwargs.get(name) for name in argnameset] assert sum(val is not None for val in vals) == 1
--- a/eric7/DebugClients/Python/coverage/multiproc.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/multiproc.py Sat Aug 21 14:21:44 2021 +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=signature-differs + def _bootstrap(self, *args, **kwargs): """Wrapper around _bootstrap to start coverage.""" try: from coverage import Coverage # avoid circular import
--- a/eric7/DebugClients/Python/coverage/optional.py Fri Aug 20 19:56:17 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -""" -Imports that we need at runtime, but might not be present. - -When importing one of these modules, always do it in the function where you -need the module. Some tests will need to remove the module. If you import -it at the top level of your module, then the test won't be able to simulate -the module being unimportable. - -The import will always succeed, but the value will be None if the module is -unavailable. - -Bad:: - - # MyModule.py - import unsure - - def use_unsure(): - unsure.something() - -Also bad:: - - # MyModule.py - from coverage.optional import unsure - - def use_unsure(): - unsure.something() - -Good:: - - # MyModule.py - - def use_unsure(): - from coverage.optional import unsure - if unsure is None: - raise Exception("Module unsure isn't available!") - - unsure.something() - -""" - -import contextlib - -# This file's purpose is to provide modules to be imported from here. -# pylint: disable=unused-import - -# TOML support is an install-time extra option. -try: - import toml -except ImportError: # pragma: not covered - toml = None - - -@contextlib.contextmanager -def without(modname): - """Hide a module for testing. - - Use this in a test function to make an optional module unavailable during - the test:: - - with coverage.optional.without('toml'): - use_toml_somehow() - - Arguments: - modname (str): the name of a module importable from - `coverage.optional`. - - """ - real_module = globals()[modname] - try: - globals()[modname] = None - yield - finally: - globals()[modname] = real_module
--- a/eric7/DebugClients/Python/coverage/parser.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/parser.py Sat Aug 21 14:21:44 2021 +0200 @@ -205,6 +205,12 @@ if not empty: self.raw_statements.update(self.byte_parser._find_statements()) + # The first line of modules can lie and say 1 always, even if the first + # line of code is later. If so, map 1 to the actual first line of the + # module. + if env.PYBEHAVIOR.module_firstline_1 and self._multiline: + self._multiline[1] = min(self.raw_statements) + def first_line(self, line): """Return the first line number of the statement including `line`.""" if line < 0: @@ -220,7 +226,7 @@ Returns a set of the first lines. """ - return set(self.first_line(l) for l in lines) + return {self.first_line(l) for l in lines} def translate_lines(self, lines): """Implement `FileReporter.translate_lines`.""" @@ -332,9 +338,7 @@ fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)]) msgs = [] - for fragment_pair in fragment_pairs: - smsg, emsg = fragment_pair - + for smsg, emsg in fragment_pairs: if emsg is None: if end < 0: # Hmm, maybe we have a one-line callable, let's check. @@ -389,34 +393,35 @@ """ return (ByteParser(self.text, code=c) for c in code_objects(self.code)) - def _bytes_lines(self): - """Map byte offsets to line numbers in `code`. - - Uses co_lnotab described in Python/compile.c to map byte offsets to - line numbers. Produces a sequence: (b0, l0), (b1, l1), ... + def _line_numbers(self): + """Yield the line numbers possible in this code object. - Only byte offsets that correspond to line numbers are included in the - results. - + Uses co_lnotab described in Python/compile.c to find the + line numbers. Produces a sequence: l0, l1, ... """ - # Adapted from dis.py in the standard library. - byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) - line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) + if hasattr(self.code, "co_lines"): + for _, _, line in self.code.co_lines(): + if line is not None: + yield line + else: + # Adapted from dis.py in the standard library. + byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) + line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) - last_line_num = None - line_num = self.code.co_firstlineno - byte_num = 0 - for byte_incr, line_incr in zip(byte_increments, line_increments): - if byte_incr: - if line_num != last_line_num: - yield (byte_num, line_num) - last_line_num = line_num - byte_num += byte_incr - if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: - line_incr -= 0x100 - line_num += line_incr - if line_num != last_line_num: - yield (byte_num, line_num) + last_line_num = None + line_num = self.code.co_firstlineno + byte_num = 0 + for byte_incr, line_incr in zip(byte_increments, line_increments): + if byte_incr: + if line_num != last_line_num: + yield line_num + last_line_num = line_num + byte_num += byte_incr + if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: + line_incr -= 0x100 + line_num += line_incr + if line_num != last_line_num: + yield line_num def _find_statements(self): """Find the statements in `self.code`. @@ -427,7 +432,7 @@ """ for bp in self.child_parsers(): # Get all of the lineno information from this code. - for _, l in bp._bytes_lines(): + for l in bp._line_numbers(): yield l @@ -520,7 +525,7 @@ def __init__(self, text, statements, multiline): self.root_node = ast.parse(neuter_encoding_declaration(text)) # TODO: I think this is happening in too many places. - self.statements = set(multiline.get(l, l) for l in statements) + self.statements = {multiline.get(l, l) for l in statements} self.multiline = multiline if AST_DUMP: # pragma: debugging @@ -619,17 +624,19 @@ return node.lineno def _line__Module(self, node): - if node.body: + if env.PYBEHAVIOR.module_firstline_1: + return 1 + elif node.body: return self.line_for_node(node.body[0]) else: # Empty modules have no line number, they always start at 1. return 1 # The node types that just flow to the next node with no complications. - OK_TO_DEFAULT = set([ + OK_TO_DEFAULT = { "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", "Import", "ImportFrom", "Nonlocal", "Pass", "Print", - ]) + } @contract(returns='ArcStarts') def add_arcs(self, node): @@ -661,7 +668,7 @@ print("*** Unhandled: {}".format(node)) # Default for simple statements: one exit from this node. - return set([ArcStart(self.line_for_node(node))]) + return {ArcStart(self.line_for_node(node))} @one_of("from_start, prev_starts") @contract(returns='ArcStarts') @@ -677,7 +684,7 @@ """ if prev_starts is None: - prev_starts = set([from_start]) + prev_starts = {from_start} for body_node in body: lineno = self.line_for_node(body_node) first_line = self.multiline.get(lineno, lineno) @@ -890,7 +897,7 @@ self.add_arc(last, lineno) last = lineno # The body is handled in collect_arcs. - return set([ArcStart(last)]) + return {ArcStart(last)} _handle__ClassDef = _handle_decorated @@ -984,7 +991,7 @@ # If there are `except` clauses, then raises in the try body # will already jump to them. Start this set over for raises in # `except` and `else`. - try_block.raise_from = set([]) + try_block.raise_from = set() else: self.block_stack.pop() @@ -1079,7 +1086,7 @@ if start.cause is not None: causes.append(start.cause.format(lineno=start.lineno)) cause = " or ".join(causes) - exits = set(ArcStart(xit.lineno, cause) for xit in exits) + exits = {ArcStart(xit.lineno, cause) for xit in exits} return exits @contract(returns='ArcStarts') @@ -1109,9 +1116,14 @@ @contract(returns='ArcStarts') def _handle__While(self, node): + start = to_top = self.line_for_node(node.test) constant_test = self.is_constant_expr(node.test) - start = to_top = self.line_for_node(node.test) + top_is_body0 = False if constant_test and (env.PY3 or constant_test == "Num"): + top_is_body0 = True + if env.PYBEHAVIOR.keep_constant_test: + top_is_body0 = False + if top_is_body0: to_top = self.line_for_node(node.body[0]) self.block_stack.append(LoopBlock(start=to_top)) from_start = ArcStart(start, cause="the condition on line {lineno} was never true")
--- a/eric7/DebugClients/Python/coverage/phystokens.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/phystokens.py Sat Aug 21 14:21:44 2021 +0200 @@ -87,7 +87,7 @@ """ - ws_tokens = set([token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL]) + ws_tokens = {token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL} line = [] col = 0
--- a/eric7/DebugClients/Python/coverage/pytracer.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/pytracer.py Sat Aug 21 14:21:44 2021 +0200 @@ -14,6 +14,11 @@ if env.PY2: YIELD_VALUE = chr(YIELD_VALUE) +# When running meta-coverage, this file can try to trace itself, which confuses +# everything. Don't trace ourselves. + +THIS_FILE = __file__.rstrip("co") + class PyTracer(object): """Python implementation of the raw data tracer.""" @@ -72,25 +77,47 @@ def log(self, marker, *args): """For hard-core logging of what this tracer is doing.""" with open("/tmp/debug_trace.txt", "a") as f: - f.write("{} {:x}.{:x}[{}] {:x} {}\n".format( + f.write("{} {}[{}]".format( marker, id(self), - self.thread.ident, len(self.data_stack), - self.threading.currentThread().ident, - " ".join(map(str, args)) )) + if 0: + f.write(".{:x}.{:x}".format( + self.thread.ident, + self.threading.currentThread().ident, + )) + f.write(" {}".format(" ".join(map(str, args)))) + if 0: + f.write(" | ") + stack = " / ".join( + (fname or "???").rpartition("/")[-1] + for _, fname, _, _ in self.data_stack + ) + f.write(stack) + f.write("\n") def _trace(self, frame, event, arg_unused): """The trace function passed to sys.settrace.""" - #self.log(":", frame.f_code.co_filename, frame.f_lineno, event) + if THIS_FILE in frame.f_code.co_filename: + return None + + #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event) if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable # The PyTrace.stop() method has been called, possibly by another # thread, let's deactivate ourselves now. - #self.log("X", frame.f_code.co_filename, frame.f_lineno) + if 0: + self.log("---\nX", frame.f_code.co_filename, frame.f_lineno) + f = frame + while f: + self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace) + f = f.f_back sys.settrace(None) + self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( + self.data_stack.pop() + ) return None if self.last_exc_back: @@ -104,10 +131,13 @@ ) self.last_exc_back = None + # if event != 'call' and frame.f_code.co_filename != self.cur_file_name: + # self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno) + if event == 'call': # Should we start a new context? if self.should_start_context and self.context is None: - context_maybe = self.should_start_context(frame) # pylint: disable=not-callable + context_maybe = self.should_start_context(frame) if context_maybe is not None: self.context = context_maybe self.started_context = True @@ -132,15 +162,15 @@ self.cur_file_name = filename disp = self.should_trace_cache.get(filename) if disp is None: - disp = self.should_trace(filename, frame) # pylint: disable=not-callable - self.should_trace_cache[filename] = disp # pylint: disable=unsupported-assignment-operation + disp = self.should_trace(filename, frame) + self.should_trace_cache[filename] = disp self.cur_file_dict = None if disp.trace: tracename = disp.source_filename - if tracename not in self.data: # pylint: disable=unsupported-membership-test - self.data[tracename] = {} # pylint: disable=unsupported-assignment-operation - self.cur_file_dict = self.data[tracename] # pylint: disable=unsubscriptable-object + if tracename not in self.data: + self.data[tracename] = {} + self.cur_file_dict = self.data[tracename] # The call event is really a "start frame" event, and happens for # function calls and re-entering generators. The f_lasti field is # -1 for calls, and a real offset for generators. Use <0 as the @@ -153,8 +183,7 @@ # Record an executed line. if self.cur_file_dict is not None: lineno = frame.f_lineno - #if frame.f_code.co_filename != self.cur_file_name: - # self.log("*", frame.f_code.co_filename, self.cur_file_name, lineno) + if self.trace_arcs: self.cur_file_dict[(self.last_line, lineno)] = None else: @@ -227,7 +256,7 @@ # has changed to None. dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable - self.warn( # pylint: disable=not-callable + self.warn( "Trace function changed, measurement is likely wrong: %r" % (tf,), slug="trace-changed", )
--- a/eric7/DebugClients/Python/coverage/results.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/results.py Sat Aug 21 14:21:44 2021 +0200 @@ -146,10 +146,7 @@ stats = {} for lnum in self._branch_lines(): exits = self.exit_counts[lnum] - try: - missing = len(missing_arcs[lnum]) - except KeyError: - missing = 0 + missing = len(missing_arcs[lnum]) stats[lnum] = (exits, exits - missing) return stats @@ -265,7 +262,7 @@ # Implementing 0+Numbers allows us to sum() a list of Numbers. if other == 0: return self - return NotImplemented + return NotImplemented # pragma: not covered (we never call it this way) def _line_ranges(statements, lines): @@ -315,7 +312,7 @@ line_exits = sorted(arcs) for line, exits in line_exits: for ex in sorted(exits): - if line not in lines: + if line not in lines and ex not in lines: dest = (ex if ex > 0 else "exit") line_items.append((line, "%d->%s" % (line, dest)))
--- a/eric7/DebugClients/Python/coverage/sqldata.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/sqldata.py Sat Aug 21 14:21:44 2021 +0200 @@ -486,9 +486,9 @@ assert lines or arcs assert not (lines and arcs) if lines and self._has_arcs: - raise CoverageException("Can't add lines to existing arc data") + raise CoverageException("Can't add line measurements to existing branch data") if arcs and self._has_lines: - raise CoverageException("Can't add arcs to existing line data") + raise CoverageException("Can't add branch measurements to existing line data") if not self._has_arcs and not self._has_lines: self._has_lines = lines self._has_arcs = arcs @@ -784,7 +784,7 @@ """ self._start_using() with self._connect() as con: - contexts = set(row[0] for row in con.execute("select distinct(context) from context")) + contexts = {row[0] for row in con.execute("select distinct(context) from context")} return contexts def file_tracer(self, filename): @@ -857,7 +857,7 @@ arcs = self.arcs(filename) if arcs is not None: all_lines = itertools.chain.from_iterable(arcs) - return list(set(l for l in all_lines if l > 0)) + return list({l for l in all_lines if l > 0}) with self._connect() as con: file_id = self._file_id(filename)
--- a/eric7/DebugClients/Python/coverage/summary.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/summary.py Sat Aug 21 14:21:44 2021 +0200 @@ -8,7 +8,7 @@ from coverage import env from coverage.report import get_analysis_to_report from coverage.results import Numbers -from coverage.misc import NotPython, CoverageException, output_encoding +from coverage.misc import CoverageException, output_encoding class SummaryReporter(object): @@ -78,29 +78,18 @@ lines = [] for (fr, analysis) in self.fr_analysis: - try: - nums = analysis.numbers + nums = analysis.numbers - args = (fr.relative_filename(), nums.n_statements, nums.n_missing) - if self.branches: - args += (nums.n_branches, nums.n_partial_branches) - args += (nums.pc_covered_str,) - if self.config.show_missing: - args += (analysis.missing_formatted(branches=True),) - text = fmt_coverage % args - # Add numeric percent coverage so that sorting makes sense. - args += (nums.pc_covered,) - lines.append((text, args)) - except Exception: - report_it = not self.config.ignore_errors - if report_it: - typ, msg = sys.exc_info()[:2] - # NotPython is only raised by PythonFileReporter, which has a - # should_be_python() method. - if typ is NotPython and not fr.should_be_python(): - report_it = False - if report_it: - self.writeout(self.fmt_err % (fr.relative_filename(), typ.__name__, msg)) + args = (fr.relative_filename(), nums.n_statements, nums.n_missing) + if self.branches: + args += (nums.n_branches, nums.n_partial_branches) + args += (nums.pc_covered_str,) + if self.config.show_missing: + args += (analysis.missing_formatted(branches=True),) + text = fmt_coverage % args + # Add numeric percent coverage so that sorting makes sense. + args += (nums.pc_covered,) + lines.append((text, args)) # Sort the lines and write them out. if getattr(self.config, 'sort', None): @@ -120,8 +109,8 @@ for line in lines: self.writeout(line[0]) - # Write a TOTAl line if we had more than one file. - if self.total.n_files > 1: + # Write a TOTAL line if we had at least one file. + if self.total.n_files > 0: self.writeout(rule) args = ("TOTAL", self.total.n_statements, self.total.n_missing) if self.branches:
--- a/eric7/DebugClients/Python/coverage/tomlconfig.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/tomlconfig.py Sat Aug 21 14:21:44 2021 +0200 @@ -11,6 +11,12 @@ from coverage.backward import configparser, path_types from coverage.misc import CoverageException, substitute_variables +# TOML support is an install-time extra option. +try: + import toml +except ImportError: # pragma: not covered + toml = None + class TomlDecodeError(Exception): """An exception class that exists even when toml isn't installed.""" @@ -29,8 +35,6 @@ self.data = None def read(self, filenames): - from coverage.optional import toml - # RawConfigParser takes a filename or list of filenames, but we only # ever call this with a single filename. assert isinstance(filenames, path_types)
--- a/eric7/DebugClients/Python/coverage/version.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/version.py Sat Aug 21 14:21:44 2021 +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, 3, 1, "final", 0) +version_info = (5, 5, 0, "final", 0) def _make_version(major, minor, micro, releaselevel, serial):