summaryrefslogtreecommitdiffstats
path: root/third_party/python/coverage
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/coverage')
-rw-r--r--third_party/python/coverage/.editorconfig44
-rw-r--r--third_party/python/coverage/.readthedocs.yml22
-rw-r--r--third_party/python/coverage/.travis.yml52
-rw-r--r--third_party/python/coverage/CHANGES.rst2743
-rw-r--r--third_party/python/coverage/CONTRIBUTORS.txt136
-rw-r--r--third_party/python/coverage/LICENSE.txt177
-rw-r--r--third_party/python/coverage/MANIFEST.in49
-rw-r--r--third_party/python/coverage/Makefile162
-rw-r--r--third_party/python/coverage/NOTICE.txt14
-rw-r--r--third_party/python/coverage/PKG-INFO187
-rw-r--r--third_party/python/coverage/README.rst152
-rw-r--r--third_party/python/coverage/__main__.py12
-rw-r--r--third_party/python/coverage/appveyor.yml169
-rw-r--r--third_party/python/coverage/ci/README.txt1
-rw-r--r--third_party/python/coverage/ci/download_appveyor.py95
-rw-r--r--third_party/python/coverage/ci/install.ps1203
-rwxr-xr-xthird_party/python/coverage/ci/manylinux.sh60
-rw-r--r--third_party/python/coverage/ci/run_with_env.cmd91
-rw-r--r--third_party/python/coverage/ci/upload_relnotes.py122
-rw-r--r--third_party/python/coverage/coverage/__init__.py36
-rw-r--r--third_party/python/coverage/coverage/__main__.py8
-rw-r--r--third_party/python/coverage/coverage/annotate.py108
-rw-r--r--third_party/python/coverage/coverage/backunittest.py33
-rw-r--r--third_party/python/coverage/coverage/backward.py253
-rw-r--r--third_party/python/coverage/coverage/bytecode.py19
-rw-r--r--third_party/python/coverage/coverage/cmdline.py866
-rw-r--r--third_party/python/coverage/coverage/collector.py429
-rw-r--r--third_party/python/coverage/coverage/config.py555
-rw-r--r--third_party/python/coverage/coverage/context.py91
-rw-r--r--third_party/python/coverage/coverage/control.py1110
-rw-r--r--third_party/python/coverage/coverage/ctracer/datastack.c50
-rw-r--r--third_party/python/coverage/coverage/ctracer/datastack.h45
-rw-r--r--third_party/python/coverage/coverage/ctracer/filedisp.c85
-rw-r--r--third_party/python/coverage/coverage/ctracer/filedisp.h26
-rw-r--r--third_party/python/coverage/coverage/ctracer/module.c108
-rw-r--r--third_party/python/coverage/coverage/ctracer/stats.h31
-rw-r--r--third_party/python/coverage/coverage/ctracer/tracer.c1186
-rw-r--r--third_party/python/coverage/coverage/ctracer/tracer.h74
-rw-r--r--third_party/python/coverage/coverage/ctracer/util.h67
-rw-r--r--third_party/python/coverage/coverage/data.py124
-rw-r--r--third_party/python/coverage/coverage/debug.py406
-rw-r--r--third_party/python/coverage/coverage/disposition.py37
-rw-r--r--third_party/python/coverage/coverage/env.py99
-rw-r--r--third_party/python/coverage/coverage/execfile.py362
-rw-r--r--third_party/python/coverage/coverage/files.py432
-rw-r--r--third_party/python/coverage/coverage/fullcoverage/encodings.py60
-rw-r--r--third_party/python/coverage/coverage/html.py511
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/coverage_html.js589
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/index.html118
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js9
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/jquery.hotkeys.js99
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/jquery.isonscreen.js53
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/jquery.min.js4
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/jquery.tablesorter.min.js2
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/keybd_closed.pngbin0 -> 112 bytes
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/keybd_open.pngbin0 -> 112 bytes
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/pyfile.html112
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/style.css124
-rw-r--r--third_party/python/coverage/coverage/htmlfiles/style.scss537
-rw-r--r--third_party/python/coverage/coverage/inorout.py469
-rw-r--r--third_party/python/coverage/coverage/jsonreport.py103
-rw-r--r--third_party/python/coverage/coverage/misc.py361
-rw-r--r--third_party/python/coverage/coverage/multiproc.py111
-rw-r--r--third_party/python/coverage/coverage/numbits.py163
-rw-r--r--third_party/python/coverage/coverage/optional.py68
-rw-r--r--third_party/python/coverage/coverage/parser.py1251
-rw-r--r--third_party/python/coverage/coverage/phystokens.py297
-rw-r--r--third_party/python/coverage/coverage/plugin.py533
-rw-r--r--third_party/python/coverage/coverage/plugin_support.py281
-rw-r--r--third_party/python/coverage/coverage/python.py249
-rw-r--r--third_party/python/coverage/coverage/pytracer.py245
-rw-r--r--third_party/python/coverage/coverage/report.py86
-rw-r--r--third_party/python/coverage/coverage/results.py346
-rw-r--r--third_party/python/coverage/coverage/sqldata.py1106
-rw-r--r--third_party/python/coverage/coverage/summary.py155
-rw-r--r--third_party/python/coverage/coverage/templite.py302
-rw-r--r--third_party/python/coverage/coverage/tomlconfig.py164
-rw-r--r--third_party/python/coverage/coverage/version.py33
-rw-r--r--third_party/python/coverage/coverage/xmlreport.py230
-rw-r--r--third_party/python/coverage/howto.txt122
-rw-r--r--third_party/python/coverage/igor.py395
-rw-r--r--third_party/python/coverage/metacov.ini88
-rw-r--r--third_party/python/coverage/pylintrc335
-rw-r--r--third_party/python/coverage/requirements/ci.pip8
-rw-r--r--third_party/python/coverage/requirements/dev.pip24
-rw-r--r--third_party/python/coverage/requirements/pytest.pip21
-rw-r--r--third_party/python/coverage/requirements/tox.pip7
-rw-r--r--third_party/python/coverage/requirements/wheel.pip7
-rw-r--r--third_party/python/coverage/setup.cfg19
-rw-r--r--third_party/python/coverage/setup.py217
-rw-r--r--third_party/python/coverage/tox.ini95
-rw-r--r--third_party/python/coverage/tox_wheels.ini21
92 files changed, 21261 insertions, 0 deletions
diff --git a/third_party/python/coverage/.editorconfig b/third_party/python/coverage/.editorconfig
new file mode 100644
index 0000000000..f560af7444
--- /dev/null
+++ b/third_party/python/coverage/.editorconfig
@@ -0,0 +1,44 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# This file is for unifying the coding style for different editors and IDEs.
+# More information at http://EditorConfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+max_line_length = 80
+trim_trailing_whitespace = true
+
+[*.py]
+max_line_length = 100
+
+[*.c]
+max_line_length = 100
+
+[*.h]
+max_line_length = 100
+
+[*.yml]
+indent_size = 2
+
+[*.rst]
+max_line_length = 79
+
+[Makefile]
+indent_style = tab
+indent_size = 8
+
+[*,cover]
+trim_trailing_whitespace = false
+
+[*.diff]
+trim_trailing_whitespace = false
+
+[.git/*]
+trim_trailing_whitespace = false
diff --git a/third_party/python/coverage/.readthedocs.yml b/third_party/python/coverage/.readthedocs.yml
new file mode 100644
index 0000000000..ed3737fbe7
--- /dev/null
+++ b/third_party/python/coverage/.readthedocs.yml
@@ -0,0 +1,22 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+#
+# ReadTheDocs configuration.
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html
+
+version: 2
+
+sphinx:
+ builder: html
+ configuration: doc/conf.py
+
+# No other formats than HTML
+formats: []
+
+python:
+ version: 3.7
+ install:
+ - requirements: doc/requirements.pip
+ - method: pip
+ path: .
+ system_packages: false
diff --git a/third_party/python/coverage/.travis.yml b/third_party/python/coverage/.travis.yml
new file mode 100644
index 0000000000..f5e8fad19d
--- /dev/null
+++ b/third_party/python/coverage/.travis.yml
@@ -0,0 +1,52 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+#
+# Tell Travis what to do
+# https://travis-ci.com/nedbat/coveragepy
+
+dist: xenial
+language: python
+
+cache: pip
+
+python:
+ - '2.7'
+ - '3.5'
+ - '3.6'
+ - '3.7'
+ - '3.8'
+ - 'pypy2.7-6.0'
+ - 'pypy3.5-6.0'
+
+# Only testing it for python3.8 on aarch64 platform, since it already has a lot
+# of jobs to test and takes long time.
+matrix:
+ include:
+ - python: 3.8
+ arch: arm64
+ env:
+ - COVERAGE_COVERAGE=no
+ - python: 3.8
+ arch: arm64
+ env:
+ - COVERAGE_COVERAGE=yes
+
+env:
+ matrix:
+ - COVERAGE_COVERAGE=no
+ - COVERAGE_COVERAGE=yes
+
+install:
+ - pip install -r requirements/ci.pip
+ - pip freeze
+
+script:
+ - tox
+
+after_script:
+ - |
+ if [[ $COVERAGE_COVERAGE == 'yes' ]]; then
+ python igor.py combine_html
+ pip install codecov
+ codecov -X gcov --file coverage.xml
+ fi
diff --git a/third_party/python/coverage/CHANGES.rst b/third_party/python/coverage/CHANGES.rst
new file mode 100644
index 0000000000..cd34eab809
--- /dev/null
+++ b/third_party/python/coverage/CHANGES.rst
@@ -0,0 +1,2743 @@
+.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+==============================
+Change history for coverage.py
+==============================
+
+These changes are listed in decreasing version number order. Note this can be
+different from a strict chronological order when there are two branches in
+development at the same time, such as 4.5.x and 5.0.
+
+This list is detailed and covers changes in each pre-release version. If you
+want to know what's different in 5.0 since 4.5.x, see :ref:`whatsnew5x`.
+
+
+ .. When updating the "Unreleased" header to a specific version, use this
+ .. format. Don't forget the jump target:
+ ..
+ .. .. _changes_981:
+ ..
+ .. Version 9.8.1 --- 2027-07-27
+ .. ----------------------------
+
+
+.. _changes_51:
+
+Version 5.1 --- 2020-04-12
+--------------------------
+
+- The JSON report now includes counts of covered and missing branches. Thanks,
+ Salvatore Zagaria.
+
+- On Python 3.8, try-finally-return reported wrong branch coverage with
+ decorated async functions (`issue 946`_). This is now fixed. Thanks, Kjell
+ Braden.
+
+- The :meth:`~coverage.Coverage.get_option` and
+ :meth:`~coverage.Coverage.set_option` methods can now manipulate the
+ ``[paths]`` configuration setting. Thanks to Bernát Gábor for the fix for
+ `issue 967`_.
+
+.. _issue 946: https://github.com/nedbat/coveragepy/issues/946
+.. _issue 967: https://github.com/nedbat/coveragepy/issues/967
+
+
+.. _changes_504:
+
+Version 5.0.4 --- 2020-03-16
+----------------------------
+
+- If using the ``[run] relative_files`` setting, the XML report will use
+ relative files in the ``<source>`` elements indicating the location of source
+ code. Closes `issue 948`_.
+
+- The textual summary report could report missing lines with negative line
+ numbers on PyPy3 7.1 (`issue 943`_). This is now fixed.
+
+- Windows wheels for Python 3.8 were incorrectly built, but are now fixed.
+ (`issue 949`_)
+
+- Updated Python 3.9 support to 3.9a4.
+
+- HTML reports couldn't be sorted if localStorage wasn't available. This is now
+ fixed: sorting works even though the sorting setting isn't retained. (`issue
+ 944`_ and `pull request 945`_). Thanks, Abdeali Kothari.
+
+.. _issue 943: https://github.com/nedbat/coveragepy/issues/943
+.. _issue 944: https://github.com/nedbat/coveragepy/issues/944
+.. _pull request 945: https://github.com/nedbat/coveragepy/pull/945
+.. _issue 948: https://github.com/nedbat/coveragepy/issues/948
+.. _issue 949: https://github.com/nedbat/coveragepy/issues/949
+
+
+.. _changes_503:
+
+Version 5.0.3 --- 2020-01-12
+----------------------------
+
+- A performance improvement in 5.0.2 didn't work for test suites that changed
+ directory before combining data, causing "Couldn't use data file: no such
+ table: meta" errors (`issue 916`_). This is now fixed.
+
+- Coverage could fail to run your program with some form of "ModuleNotFound" or
+ "ImportError" trying to import from the current directory. This would happen
+ if coverage had been packaged into a zip file (for example, on Windows), or
+ was found indirectly (for example, by pyenv-virtualenv). A number of
+ different scenarios were described in `issue 862`_ which is now fixed. Huge
+ thanks to Agbonze O. Jeremiah for reporting it, and Alexander Waters and
+ George-Cristian Bîrzan for protracted debugging sessions.
+
+- Added the "premain" debug option.
+
+- Added SQLite compile-time options to the "debug sys" output.
+
+.. _issue 862: https://github.com/nedbat/coveragepy/issues/862
+.. _issue 916: https://github.com/nedbat/coveragepy/issues/916
+
+
+.. _changes_502:
+
+Version 5.0.2 --- 2020-01-05
+----------------------------
+
+- Programs that used multiprocessing and changed directories would fail under
+ coverage. This is now fixed (`issue 890`_). A side effect is that debug
+ information about the config files read now shows absolute paths to the
+ files.
+
+- When running programs as modules (``coverage run -m``) with ``--source``,
+ some measured modules were imported before coverage starts. This resulted in
+ unwanted warnings ("Already imported a file that will be measured") and a
+ reduction in coverage totals (`issue 909`_). This is now fixed.
+
+- If no data was collected, an exception about "No data to report" could happen
+ instead of a 0% report being created (`issue 884`_). This is now fixed.
+
+- The handling of source files with non-encodable file names has changed.
+ Previously, if a file name could not be encoded as UTF-8, an error occurred,
+ as described in `issue 891`_. Now, those files will not be measured, since
+ their data would not be recordable.
+
+- A new warning ("dynamic-conflict") is issued if two mechanisms are trying to
+ change the dynamic context. Closes `issue 901`_.
+
+- ``coverage run --debug=sys`` would fail with an AttributeError. This is now
+ fixed (`issue 907`_).
+
+.. _issue 884: https://github.com/nedbat/coveragepy/issues/884
+.. _issue 890: https://github.com/nedbat/coveragepy/issues/890
+.. _issue 891: https://github.com/nedbat/coveragepy/issues/891
+.. _issue 901: https://github.com/nedbat/coveragepy/issues/901
+.. _issue 907: https://github.com/nedbat/coveragepy/issues/907
+.. _issue 909: https://github.com/nedbat/coveragepy/issues/909
+
+
+.. _changes_501:
+
+Version 5.0.1 --- 2019-12-22
+----------------------------
+
+- If a 4.x data file is the cause of a "file is not a database" error, then use
+ a more specific error message, "Looks like a coverage 4.x data file, are you
+ mixing versions of coverage?" Helps diagnose the problems described in
+ `issue 886`_.
+
+- Measurement contexts and relative file names didn't work together, as
+ reported in `issue 899`_ and `issue 900`_. This is now fixed, thanks to
+ David Szotten.
+
+- When using ``coverage run --concurrency=multiprocessing``, all data files
+ should be named with parallel-ready suffixes. 5.0 mistakenly named the main
+ process' file with no suffix when using ``--append``. This is now fixed,
+ closing `issue 880`_.
+
+- Fixed a problem on Windows when the current directory is changed to a
+ different drive (`issue 895`_). Thanks, Olivier Grisel.
+
+- Updated Python 3.9 support to 3.9a2.
+
+.. _issue 880: https://github.com/nedbat/coveragepy/issues/880
+.. _issue 886: https://github.com/nedbat/coveragepy/issues/886
+.. _issue 895: https://github.com/nedbat/coveragepy/issues/895
+.. _issue 899: https://github.com/nedbat/coveragepy/issues/899
+.. _issue 900: https://github.com/nedbat/coveragepy/issues/900
+
+
+.. _changes_50:
+
+Version 5.0 --- 2019-12-14
+--------------------------
+
+Nothing new beyond 5.0b2.
+
+
+.. _changes_50b2:
+
+Version 5.0b2 --- 2019-12-08
+----------------------------
+
+- An experimental ``[run] relative_files`` setting tells coverage to store
+ relative file names in the data file. This makes it easier to run tests in
+ one (or many) environments, and then report in another. It has not had much
+ real-world testing, so it may change in incompatible ways in the future.
+
+- When constructing a :class:`coverage.Coverage` object, `data_file` can be
+ specified as None to prevent writing any data file at all. In previous
+ versions, an explicit `data_file=None` argument would use the default of
+ ".coverage". Fixes `issue 871`_.
+
+- Python files run with ``-m`` now have ``__spec__`` defined properly. This
+ fixes `issue 745`_ (about not being able to run unittest tests that spawn
+ subprocesses), and `issue 838`_, which described the problem directly.
+
+- The ``[paths]`` configuration section is now ordered. If you specify more
+ than one list of patterns, the first one that matches will be used. Fixes
+ `issue 649`_.
+
+- The :func:`.coverage.numbits.register_sqlite_functions` function now also
+ registers `numbits_to_nums` for use in SQLite queries. Thanks, Simon
+ Willison.
+
+- Python 3.9a1 is supported.
+
+- Coverage.py has a mascot: :ref:`Sleepy Snake <sleepy>`.
+
+.. _issue 649: https://github.com/nedbat/coveragepy/issues/649
+.. _issue 745: https://github.com/nedbat/coveragepy/issues/745
+.. _issue 838: https://github.com/nedbat/coveragepy/issues/838
+.. _issue 871: https://github.com/nedbat/coveragepy/issues/871
+
+
+.. _changes_50b1:
+
+Version 5.0b1 --- 2019-11-11
+----------------------------
+
+- The HTML and textual reports now have a ``--skip-empty`` option that skips
+ files with no statements, notably ``__init__.py`` files. Thanks, Reya B.
+
+- Configuration can now be read from `TOML`_ files. This requires installing
+ coverage.py with the ``[toml]`` extra. The standard "pyproject.toml" file
+ will be read automatically if no other configuration file is found, with
+ settings in the ``[tool.coverage.]`` namespace. Thanks to Frazer McLean for
+ implementation and persistence. Finishes `issue 664`_.
+
+- The ``[run] note`` setting has been deprecated. Using it will result in a
+ warning, and the note will not be written to the data file. The
+ corresponding :class:`.CoverageData` methods have been removed.
+
+- The HTML report has been reimplemented (no more table around the source
+ code). This allowed for a better presentation of the context information,
+ hopefully resolving `issue 855`_.
+
+- Added sqlite3 module version information to ``coverage debug sys`` output.
+
+- Asking the HTML report to show contexts (``[html] show_contexts=True`` or
+ ``coverage html --show-contexts``) will issue a warning if there were no
+ contexts measured (`issue 851`_).
+
+.. _TOML: https://github.com/toml-lang/toml#readme
+.. _issue 664: https://github.com/nedbat/coveragepy/issues/664
+.. _issue 851: https://github.com/nedbat/coveragepy/issues/851
+.. _issue 855: https://github.com/nedbat/coveragepy/issues/855
+
+
+.. _changes_50a8:
+
+Version 5.0a8 --- 2019-10-02
+----------------------------
+
+- The :class:`.CoverageData` API has changed how queries are limited to
+ specific contexts. Now you use :meth:`.CoverageData.set_query_context` to
+ set a single exact-match string, or :meth:`.CoverageData.set_query_contexts`
+ to set a list of regular expressions to match contexts. This changes the
+ command-line ``--contexts`` option to use regular expressions instead of
+ filename-style wildcards.
+
+
+.. _changes_50a7:
+
+Version 5.0a7 --- 2019-09-21
+----------------------------
+
+- Data can now be "reported" in JSON format, for programmatic use, as requested
+ in `issue 720`_. The new ``coverage json`` command writes raw and summarized
+ data to a JSON file. Thanks, Matt Bachmann.
+
+- Dynamic contexts are now supported in the Python tracer, which is important
+ for PyPy users. Closes `issue 846`_.
+
+- The compact line number representation introduced in 5.0a6 is called a
+ "numbits." The :mod:`coverage.numbits` module provides functions for working
+ with them.
+
+- The reporting methods used to permanently apply their arguments to the
+ configuration of the Coverage object. Now they no longer do. The arguments
+ affect the operation of the method, but do not persist.
+
+- A class named "test_something" no longer confuses the ``test_function``
+ dynamic context setting. Fixes `issue 829`_.
+
+- Fixed an unusual tokenizing issue with backslashes in comments. Fixes
+ `issue 822`_.
+
+- ``debug=plugin`` didn't properly support configuration or dynamic context
+ plugins, but now it does, closing `issue 834`_.
+
+.. _issue 720: https://github.com/nedbat/coveragepy/issues/720
+.. _issue 822: https://github.com/nedbat/coveragepy/issues/822
+.. _issue 834: https://github.com/nedbat/coveragepy/issues/834
+.. _issue 829: https://github.com/nedbat/coveragepy/issues/829
+.. _issue 846: https://github.com/nedbat/coveragepy/issues/846
+
+
+.. _changes_50a6:
+
+Version 5.0a6 --- 2019-07-16
+----------------------------
+
+- Reporting on contexts. Big thanks to Stephan Richter and Albertas Agejevas
+ for the contribution.
+
+ - The ``--contexts`` option is available on the ``report`` and ``html``
+ commands. It's a comma-separated list of shell-style wildcards, selecting
+ the contexts to report on. Only contexts matching one of the wildcards
+ will be included in the report.
+
+ - The ``--show-contexts`` option for the ``html`` command adds context
+ information to each covered line. Hovering over the "ctx" marker at the
+ end of the line reveals a list of the contexts that covered the line.
+
+- Database changes:
+
+ - Line numbers are now stored in a much more compact way. For each file and
+ context, a single binary string is stored with a bit per line number. This
+ greatly improves memory use, but makes ad-hoc use difficult.
+
+ - Dynamic contexts with no data are no longer written to the database.
+
+ - SQLite data storage is now faster. There's no longer a reason to keep the
+ JSON data file code, so it has been removed.
+
+- Changes to the :class:`.CoverageData` interface:
+
+ - The new :meth:`.CoverageData.dumps` method serializes the data to a string,
+ and a corresponding :meth:`.CoverageData.loads` method reconstitutes this
+ data. The format of the data string is subject to change at any time, and
+ so should only be used between two installations of the same version of
+ coverage.py.
+
+ - The :meth:`CoverageData constructor<.CoverageData.__init__>` has a new
+ argument, `no_disk` (default: False). Setting it to True prevents writing
+ any data to the disk. This is useful for transient data objects.
+
+- Added the classmethod :meth:`.Coverage.current` to get the latest started
+ Coverage instance.
+
+- Multiprocessing support in Python 3.8 was broken, but is now fixed. Closes
+ `issue 828`_.
+
+- Error handling during reporting has changed slightly. All reporting methods
+ now behave the same. The ``--ignore-errors`` option keeps errors from
+ stopping the reporting, but files that couldn't parse as Python will always
+ be reported as warnings. As with other warnings, you can suppress them with
+ the ``[run] disable_warnings`` configuration setting.
+
+- Coverage.py no longer fails if the user program deletes its current
+ directory. Fixes `issue 806`_. Thanks, Dan Hemberger.
+
+- The scrollbar markers in the HTML report now accurately show the highlighted
+ lines, regardless of what categories of line are highlighted.
+
+- The hack to accommodate ShiningPanda_ looking for an obsolete internal data
+ file has been removed, since ShiningPanda 0.22 fixed it four years ago.
+
+- The deprecated `Reporter.file_reporters` property has been removed.
+
+.. _ShiningPanda: https://wiki.jenkins.io/display/JENKINS/ShiningPanda+Plugin
+.. _issue 806: https://github.com/nedbat/coveragepy/pull/806
+.. _issue 828: https://github.com/nedbat/coveragepy/issues/828
+
+
+.. _changes_50a5:
+
+Version 5.0a5 --- 2019-05-07
+----------------------------
+
+- Drop support for Python 3.4
+
+- Dynamic contexts can now be set two new ways, both thanks to Justas
+ Sadzevičius.
+
+ - A plugin can implement a ``dynamic_context`` method to check frames for
+ whether a new context should be started. See
+ :ref:`dynamic_context_plugins` for more details.
+
+ - Another tool (such as a test runner) can use the new
+ :meth:`.Coverage.switch_context` method to explicitly change the context.
+
+- The ``dynamic_context = test_function`` setting now works with Python 2
+ old-style classes, though it only reports the method name, not the class it
+ was defined on. Closes `issue 797`_.
+
+- ``fail_under`` values more than 100 are reported as errors. Thanks to Mike
+ Fiedler for closing `issue 746`_.
+
+- The "missing" values in the text output are now sorted by line number, so
+ that missing branches are reported near the other lines they affect. The
+ values used to show all missing lines, and then all missing branches.
+
+- Access to the SQLite database used for data storage is now thread-safe.
+ Thanks, Stephan Richter. This closes `issue 702`_.
+
+- Combining data stored in SQLite is now about twice as fast, fixing `issue
+ 761`_. Thanks, Stephan Richter.
+
+- The ``filename`` attribute on :class:`.CoverageData` objects has been made
+ private. You can use the ``data_filename`` method to get the actual file
+ name being used to store data, and the ``base_filename`` method to get the
+ original filename before parallelizing suffixes were added. This is part of
+ fixing `issue 708`_.
+
+- Line numbers in the HTML report now align properly with source lines, even
+ when Chrome's minimum font size is set, fixing `issue 748`_. Thanks Wen Ye.
+
+.. _issue 702: https://github.com/nedbat/coveragepy/issues/702
+.. _issue 708: https://github.com/nedbat/coveragepy/issues/708
+.. _issue 746: https://github.com/nedbat/coveragepy/issues/746
+.. _issue 748: https://github.com/nedbat/coveragepy/issues/748
+.. _issue 761: https://github.com/nedbat/coveragepy/issues/761
+.. _issue 797: https://github.com/nedbat/coveragepy/issues/797
+
+
+.. _changes_50a4:
+
+Version 5.0a4 --- 2018-11-25
+----------------------------
+
+- You can specify the command line to run your program with the ``[run]
+ command_line`` configuration setting, as requested in `issue 695`_.
+
+- Coverage will create directories as needed for the data file if they don't
+ exist, closing `issue 721`_.
+
+- The ``coverage run`` command has always adjusted the first entry in sys.path,
+ to properly emulate how Python runs your program. Now this adjustment is
+ skipped if sys.path[0] is already different than Python's default. This
+ fixes `issue 715`_.
+
+- Improvements to context support:
+
+ - The "no such table: meta" error is fixed.: `issue 716`_.
+
+ - Combining data files is now much faster.
+
+- Python 3.8 (as of today!) passes all tests.
+
+.. _issue 695: https://github.com/nedbat/coveragepy/issues/695
+.. _issue 715: https://github.com/nedbat/coveragepy/issues/715
+.. _issue 716: https://github.com/nedbat/coveragepy/issues/716
+.. _issue 721: https://github.com/nedbat/coveragepy/issues/721
+
+
+.. _changes_50a3:
+
+Version 5.0a3 --- 2018-10-06
+----------------------------
+
+- Context support: static contexts let you specify a label for a coverage run,
+ which is recorded in the data, and retained when you combine files. See
+ :ref:`contexts` for more information.
+
+- Dynamic contexts: specifying ``[run] dynamic_context = test_function`` in the
+ config file will record the test function name as a dynamic context during
+ execution. This is the core of "Who Tests What" (`issue 170`_). Things to
+ note:
+
+ - There is no reporting support yet. Use SQLite to query the .coverage file
+ for information. Ideas are welcome about how reporting could be extended
+ to use this data.
+
+ - There's a noticeable slow-down before any test is run.
+
+ - Data files will now be roughly N times larger, where N is the number of
+ tests you have. Combining data files is therefore also N times slower.
+
+ - No other values for ``dynamic_context`` are recognized yet. Let me know
+ what else would be useful. I'd like to use a pytest plugin to get better
+ information directly from pytest, for example.
+
+.. _issue 170: https://github.com/nedbat/coveragepy/issues/170
+
+- Environment variable substitution in configuration files now supports two
+ syntaxes for controlling the behavior of undefined variables: if ``VARNAME``
+ is not defined, ``${VARNAME?}`` will raise an error, and ``${VARNAME-default
+ value}`` will use "default value".
+
+- Partial support for Python 3.8, which has not yet released an alpha. Fixes
+ `issue 707`_ and `issue 714`_.
+
+.. _issue 707: https://github.com/nedbat/coveragepy/issues/707
+.. _issue 714: https://github.com/nedbat/coveragepy/issues/714
+
+
+.. _changes_50a2:
+
+Version 5.0a2 --- 2018-09-03
+----------------------------
+
+- Coverage's data storage has changed. In version 4.x, .coverage files were
+ basically JSON. Now, they are SQLite databases. This means the data file
+ can be created earlier than it used to. A large amount of code was
+ refactored to support this change.
+
+ - Because the data file is created differently than previous releases, you
+ may need ``parallel=true`` where you didn't before.
+
+ - The old data format is still available (for now) by setting the environment
+ variable COVERAGE_STORAGE=json. Please tell me if you think you need to
+ keep the JSON format.
+
+ - The database schema is guaranteed to change in the future, to support new
+ features. I'm looking for opinions about making the schema part of the
+ public API to coverage.py or not.
+
+- Development moved from `Bitbucket`_ to `GitHub`_.
+
+- HTML files no longer have trailing and extra whitespace.
+
+- The sort order in the HTML report is stored in local storage rather than
+ cookies, closing `issue 611`_. Thanks, Federico Bond.
+
+- pickle2json, for converting v3 data files to v4 data files, has been removed.
+
+.. _Bitbucket: https://bitbucket.org/ned/coveragepy
+.. _GitHub: https://github.com/nedbat/coveragepy
+
+.. _issue 611: https://github.com/nedbat/coveragepy/issues/611
+
+
+.. _changes_50a1:
+
+Version 5.0a1 --- 2018-06-05
+----------------------------
+
+- Coverage.py no longer supports Python 2.6 or 3.3.
+
+- The location of the configuration file can now be specified with a
+ ``COVERAGE_RCFILE`` environment variable, as requested in `issue 650`_.
+
+- Namespace packages are supported on Python 3.7, where they used to cause
+ TypeErrors about path being None. Fixes `issue 700`_.
+
+- A new warning (``already-imported``) is issued if measurable files have
+ already been imported before coverage.py started measurement. See
+ :ref:`cmd_warnings` for more information.
+
+- Running coverage many times for small runs in a single process should be
+ faster, closing `issue 625`_. Thanks, David MacIver.
+
+- Large HTML report pages load faster. Thanks, Pankaj Pandey.
+
+.. _issue 625: https://bitbucket.org/ned/coveragepy/issues/625/lstat-dominates-in-the-case-of-small
+.. _issue 650: https://bitbucket.org/ned/coveragepy/issues/650/allow-setting-configuration-file-location
+.. _issue 700: https://github.com/nedbat/coveragepy/issues/700
+
+
+.. _changes_454:
+
+Version 4.5.4 --- 2019-07-29
+----------------------------
+
+- Multiprocessing support in Python 3.8 was broken, but is now fixed. Closes
+ `issue 828`_.
+
+.. _issue 828: https://github.com/nedbat/coveragepy/issues/828
+
+
+.. _changes_453:
+
+Version 4.5.3 --- 2019-03-09
+----------------------------
+
+- Only packaging metadata changes.
+
+
+.. _changes_452:
+
+Version 4.5.2 --- 2018-11-12
+----------------------------
+
+- Namespace packages are supported on Python 3.7, where they used to cause
+ TypeErrors about path being None. Fixes `issue 700`_.
+
+- Python 3.8 (as of today!) passes all tests. Fixes `issue 707`_ and
+ `issue 714`_.
+
+- Development moved from `Bitbucket`_ to `GitHub`_.
+
+.. _issue 700: https://github.com/nedbat/coveragepy/issues/700
+.. _issue 707: https://github.com/nedbat/coveragepy/issues/707
+.. _issue 714: https://github.com/nedbat/coveragepy/issues/714
+
+.. _Bitbucket: https://bitbucket.org/ned/coveragepy
+.. _GitHub: https://github.com/nedbat/coveragepy
+
+
+.. _changes_451:
+
+Version 4.5.1 --- 2018-02-10
+----------------------------
+
+- Now that 4.5 properly separated the ``[run] omit`` and ``[report] omit``
+ settings, an old bug has become apparent. If you specified a package name
+ for ``[run] source``, then omit patterns weren't matched inside that package.
+ This bug (`issue 638`_) is now fixed.
+
+- On Python 3.7, reporting about a decorated function with no body other than a
+ docstring would crash coverage.py with an IndexError (`issue 640`_). This is
+ now fixed.
+
+- Configurer plugins are now reported in the output of ``--debug=sys``.
+
+.. _issue 638: https://bitbucket.org/ned/coveragepy/issues/638/run-omit-is-ignored-since-45
+.. _issue 640: https://bitbucket.org/ned/coveragepy/issues/640/indexerror-reporting-on-an-empty-decorated
+
+
+.. _changes_45:
+
+Version 4.5 --- 2018-02-03
+--------------------------
+
+- A new kind of plugin is supported: configurers are invoked at start-up to
+ allow more complex configuration than the .coveragerc file can easily do.
+ See :ref:`api_plugin` for details. This solves the complex configuration
+ problem described in `issue 563`_.
+
+- The ``fail_under`` option can now be a float. Note that you must specify the
+ ``[report] precision`` configuration option for the fractional part to be
+ used. Thanks to Lars Hupfeldt Nielsen for help with the implementation.
+ Fixes `issue 631`_.
+
+- The ``include`` and ``omit`` options can be specified for both the ``[run]``
+ and ``[report]`` phases of execution. 4.4.2 introduced some incorrect
+ interactions between those phases, where the options for one were confused
+ for the other. This is now corrected, fixing `issue 621`_ and `issue 622`_.
+ Thanks to Daniel Hahler for seeing more clearly than I could.
+
+- The ``coverage combine`` command used to always overwrite the data file, even
+ when no data had been read from apparently combinable files. Now, an error
+ is raised if we thought there were files to combine, but in fact none of them
+ could be used. Fixes `issue 629`_.
+
+- The ``coverage combine`` command could get confused about path separators
+ when combining data collected on Windows with data collected on Linux, as
+ described in `issue 618`_. This is now fixed: the result path always uses
+ the path separator specified in the ``[paths]`` result.
+
+- On Windows, the HTML report could fail when source trees are deeply nested,
+ due to attempting to create HTML filenames longer than the 250-character
+ maximum. Now filenames will never get much larger than 200 characters,
+ fixing `issue 627`_. Thanks to Alex Sandro for helping with the fix.
+
+.. _issue 563: https://bitbucket.org/ned/coveragepy/issues/563/platform-specific-configuration
+.. _issue 618: https://bitbucket.org/ned/coveragepy/issues/618/problem-when-combining-windows-generated
+.. _issue 621: https://bitbucket.org/ned/coveragepy/issues/621/include-ignored-warning-when-using
+.. _issue 622: https://bitbucket.org/ned/coveragepy/issues/622/report-omit-overwrites-run-omit
+.. _issue 627: https://bitbucket.org/ned/coveragepy/issues/627/failure-generating-html-reports-when-the
+.. _issue 629: https://bitbucket.org/ned/coveragepy/issues/629/multiple-use-of-combine-leads-to-empty
+.. _issue 631: https://bitbucket.org/ned/coveragepy/issues/631/precise-coverage-percentage-value
+
+
+.. _changes_442:
+
+Version 4.4.2 --- 2017-11-05
+----------------------------
+
+- Support for Python 3.7. In some cases, class and module docstrings are no
+ longer counted in statement totals, which could slightly change your total
+ results.
+
+- Specifying both ``--source`` and ``--include`` no longer silently ignores the
+ include setting, instead it displays a warning. Thanks, Loïc Dachary. Closes
+ `issue 265`_ and `issue 101`_.
+
+- Fixed a race condition when saving data and multiple threads are tracing
+ (`issue 581`_). It could produce a "dictionary changed size during iteration"
+ RuntimeError. I believe this mostly but not entirely fixes the race
+ condition. A true fix would likely be too expensive. Thanks, Peter Baughman
+ for the debugging, and Olivier Grisel for the fix with tests.
+
+- Configuration values which are file paths will now apply tilde-expansion,
+ closing `issue 589`_.
+
+- Now secondary config files like tox.ini and setup.cfg can be specified
+ explicitly, and prefixed sections like `[coverage:run]` will be read. Fixes
+ `issue 588`_.
+
+- Be more flexible about the command name displayed by help, fixing
+ `issue 600`_. Thanks, Ben Finney.
+
+.. _issue 101: https://bitbucket.org/ned/coveragepy/issues/101/settings-under-report-affect-running
+.. _issue 581: https://bitbucket.org/ned/coveragepy/issues/581/race-condition-when-saving-data-under
+.. _issue 588: https://bitbucket.org/ned/coveragepy/issues/588/using-rcfile-path-to-toxini-uses-run
+.. _issue 589: https://bitbucket.org/ned/coveragepy/issues/589/allow-expansion-in-coveragerc
+.. _issue 600: https://bitbucket.org/ned/coveragepy/issues/600/get-program-name-from-command-line-when
+
+
+.. _changes_441:
+
+Version 4.4.1 --- 2017-05-14
+----------------------------
+
+- No code changes: just corrected packaging for Python 2.7 Linux wheels.
+
+
+.. _changes_44:
+
+Version 4.4 --- 2017-05-07
+--------------------------
+
+- Reports could produce the wrong file names for packages, reporting ``pkg.py``
+ instead of the correct ``pkg/__init__.py``. This is now fixed. Thanks, Dirk
+ Thomas.
+
+- XML reports could produce ``<source>`` and ``<class>`` lines that together
+ didn't specify a valid source file path. This is now fixed. (`issue 526`_)
+
+- Namespace packages are no longer warned as having no code. (`issue 572`_)
+
+- Code that uses ``sys.settrace(sys.gettrace())`` in a file that wasn't being
+ coverage-measured would prevent correct coverage measurement in following
+ code. An example of this was running doctests programmatically. This is now
+ fixed. (`issue 575`_)
+
+- Errors printed by the ``coverage`` command now go to stderr instead of
+ stdout.
+
+- Running ``coverage xml`` in a directory named with non-ASCII characters would
+ fail under Python 2. This is now fixed. (`issue 573`_)
+
+.. _issue 526: https://bitbucket.org/ned/coveragepy/issues/526/generated-xml-invalid-paths-for-cobertura
+.. _issue 572: https://bitbucket.org/ned/coveragepy/issues/572/no-python-source-warning-for-namespace
+.. _issue 573: https://bitbucket.org/ned/coveragepy/issues/573/cant-generate-xml-report-if-some-source
+.. _issue 575: https://bitbucket.org/ned/coveragepy/issues/575/running-doctest-prevents-complete-coverage
+
+
+Version 4.4b1 --- 2017-04-04
+----------------------------
+
+- Some warnings can now be individually disabled. Warnings that can be
+ disabled have a short name appended. The ``[run] disable_warnings`` setting
+ takes a list of these warning names to disable. Closes both `issue 96`_ and
+ `issue 355`_.
+
+- The XML report now includes attributes from version 4 of the Cobertura XML
+ format, fixing `issue 570`_.
+
+- In previous versions, calling a method that used collected data would prevent
+ further collection. For example, `save()`, `report()`, `html_report()`, and
+ others would all stop collection. An explicit `start()` was needed to get it
+ going again. This is no longer true. Now you can use the collected data and
+ also continue measurement. Both `issue 79`_ and `issue 448`_ described this
+ problem, and have been fixed.
+
+- Plugins can now find unexecuted files if they choose, by implementing the
+ `find_executable_files` method. Thanks, Emil Madsen.
+
+- Minimal IronPython support. You should be able to run IronPython programs
+ under ``coverage run``, though you will still have to do the reporting phase
+ with CPython.
+
+- Coverage.py has long had a special hack to support CPython's need to measure
+ the coverage of the standard library tests. This code was not installed by
+ kitted versions of coverage.py. Now it is.
+
+.. _issue 79: https://bitbucket.org/ned/coveragepy/issues/79/save-prevents-harvesting-on-stop
+.. _issue 96: https://bitbucket.org/ned/coveragepy/issues/96/unhelpful-warnings-produced-when-using
+.. _issue 355: https://bitbucket.org/ned/coveragepy/issues/355/warnings-should-be-suppressable
+.. _issue 448: https://bitbucket.org/ned/coveragepy/issues/448/save-and-html_report-prevent-further
+.. _issue 570: https://bitbucket.org/ned/coveragepy/issues/570/cobertura-coverage-04dtd-support
+
+
+.. _changes_434:
+
+Version 4.3.4 --- 2017-01-17
+----------------------------
+
+- Fixing 2.6 in version 4.3.3 broke other things, because the too-tricky
+ exception wasn't properly derived from Exception, described in `issue 556`_.
+ A newb mistake; it hasn't been a good few days.
+
+.. _issue 556: https://bitbucket.org/ned/coveragepy/issues/556/43-fails-if-there-are-html-files-in-the
+
+
+.. _changes_433:
+
+Version 4.3.3 --- 2017-01-17
+----------------------------
+
+- Python 2.6 support was broken due to a testing exception imported for the
+ benefit of the coverage.py test suite. Properly conditionalizing it fixed
+ `issue 554`_ so that Python 2.6 works again.
+
+.. _issue 554: https://bitbucket.org/ned/coveragepy/issues/554/traceback-on-python-26-starting-with-432
+
+
+.. _changes_432:
+
+Version 4.3.2 --- 2017-01-16
+----------------------------
+
+- Using the ``--skip-covered`` option on an HTML report with 100% coverage
+ would cause a "No data to report" error, as reported in `issue 549`_. This is
+ now fixed; thanks, Loïc Dachary.
+
+- If-statements can be optimized away during compilation, for example, `if 0:`
+ or `if __debug__:`. Coverage.py had problems properly understanding these
+ statements which existed in the source, but not in the compiled bytecode.
+ This problem, reported in `issue 522`_, is now fixed.
+
+- If you specified ``--source`` as a directory, then coverage.py would look for
+ importable Python files in that directory, and could identify ones that had
+ never been executed at all. But if you specified it as a package name, that
+ detection wasn't performed. Now it is, closing `issue 426`_. Thanks to Loïc
+ Dachary for the fix.
+
+- If you started and stopped coverage measurement thousands of times in your
+ process, you could crash Python with a "Fatal Python error: deallocating
+ None" error. This is now fixed. Thanks to Alex Groce for the bug report.
+
+- On PyPy, measuring coverage in subprocesses could produce a warning: "Trace
+ function changed, measurement is likely wrong: None". This was spurious, and
+ has been suppressed.
+
+- Previously, coverage.py couldn't start on Jython, due to that implementation
+ missing the multiprocessing module (`issue 551`_). This problem has now been
+ fixed. Also, `issue 322`_ about not being able to invoke coverage
+ conveniently, seems much better: ``jython -m coverage run myprog.py`` works
+ properly.
+
+- Let's say you ran the HTML report over and over again in the same output
+ directory, with ``--skip-covered``. And imagine due to your heroic
+ test-writing efforts, a file just achieved the goal of 100% coverage. With
+ coverage.py 4.3, the old HTML file with the less-than-100% coverage would be
+ left behind. This file is now properly deleted.
+
+.. _issue 322: https://bitbucket.org/ned/coveragepy/issues/322/cannot-use-coverage-with-jython
+.. _issue 426: https://bitbucket.org/ned/coveragepy/issues/426/difference-between-coverage-results-with
+.. _issue 522: https://bitbucket.org/ned/coveragepy/issues/522/incorrect-branch-reporting
+.. _issue 549: https://bitbucket.org/ned/coveragepy/issues/549/skip-covered-with-100-coverage-throws-a-no
+.. _issue 551: https://bitbucket.org/ned/coveragepy/issues/551/coveragepy-cannot-be-imported-in-jython27
+
+
+.. _changes_431:
+
+Version 4.3.1 --- 2016-12-28
+----------------------------
+
+- Some environments couldn't install 4.3, as described in `issue 540`_. This is
+ now fixed.
+
+- The check for conflicting ``--source`` and ``--include`` was too simple in a
+ few different ways, breaking a few perfectly reasonable use cases, described
+ in `issue 541`_. The check has been reverted while we re-think the fix for
+ `issue 265`_.
+
+.. _issue 540: https://bitbucket.org/ned/coveragepy/issues/540/cant-install-coverage-v43-into-under
+.. _issue 541: https://bitbucket.org/ned/coveragepy/issues/541/coverage-43-breaks-nosetest-with-coverage
+
+
+.. _changes_43:
+
+Version 4.3 --- 2016-12-27
+--------------------------
+
+Special thanks to **Loïc Dachary**, who took an extraordinary interest in
+coverage.py and contributed a number of improvements in this release.
+
+- Subprocesses that are measured with `automatic subprocess measurement`_ used
+ to read in any pre-existing data file. This meant data would be incorrectly
+ carried forward from run to run. Now those files are not read, so each
+ subprocess only writes its own data. Fixes `issue 510`_.
+
+- The ``coverage combine`` command will now fail if there are no data files to
+ combine. The combine changes in 4.2 meant that multiple combines could lose
+ data, leaving you with an empty .coverage data file. Fixes
+ `issue 525`_, `issue 412`_, `issue 516`_, and probably `issue 511`_.
+
+- Coverage.py wouldn't execute `sys.excepthook`_ when an exception happened in
+ your program. Now it does, thanks to Andrew Hoos. Closes `issue 535`_.
+
+- Branch coverage fixes:
+
+ - Branch coverage could misunderstand a finally clause on a try block that
+ never continued on to the following statement, as described in `issue
+ 493`_. This is now fixed. Thanks to Joe Doherty for the report and Loïc
+ Dachary for the fix.
+
+ - A while loop with a constant condition (while True) and a continue
+ statement would be mis-analyzed, as described in `issue 496`_. This is now
+ fixed, thanks to a bug report by Eli Skeggs and a fix by Loïc Dachary.
+
+ - While loops with constant conditions that were never executed could result
+ in a non-zero coverage report. Artem Dayneko reported this in `issue
+ 502`_, and Loïc Dachary provided the fix.
+
+- The HTML report now supports a ``--skip-covered`` option like the other
+ reporting commands. Thanks, Loïc Dachary for the implementation, closing
+ `issue 433`_.
+
+- Options can now be read from a tox.ini file, if any. Like setup.cfg, sections
+ are prefixed with "coverage:", so ``[run]`` options will be read from the
+ ``[coverage:run]`` section of tox.ini. Implements part of `issue 519`_.
+ Thanks, Stephen Finucane.
+
+- Specifying both ``--source`` and ``--include`` no longer silently ignores the
+ include setting, instead it fails with a message. Thanks, Nathan Land and
+ Loïc Dachary. Closes `issue 265`_.
+
+- The ``Coverage.combine`` method has a new parameter, ``strict=False``, to
+ support failing if there are no data files to combine.
+
+- When forking subprocesses, the coverage data files would have the same random
+ number appended to the file name. This didn't cause problems, because the
+ file names had the process id also, making collisions (nearly) impossible.
+ But it was disconcerting. This is now fixed.
+
+- The text report now properly sizes headers when skipping some files, fixing
+ `issue 524`_. Thanks, Anthony Sottile and Loïc Dachary.
+
+- Coverage.py can now search .pex files for source, just as it can .zip and
+ .egg. Thanks, Peter Ebden.
+
+- Data files are now about 15% smaller.
+
+- Improvements in the ``[run] debug`` setting:
+
+ - The "dataio" debug setting now also logs when data files are deleted during
+ combining or erasing.
+
+ - A new debug option, "multiproc", for logging the behavior of
+ ``concurrency=multiprocessing``.
+
+ - If you used the debug options "config" and "callers" together, you'd get a
+ call stack printed for every line in the multi-line config output. This is
+ now fixed.
+
+- Fixed an unusual bug involving multiple coding declarations affecting code
+ containing code in multi-line strings: `issue 529`_.
+
+- Coverage.py will no longer be misled into thinking that a plain file is a
+ package when interpreting ``--source`` options. Thanks, Cosimo Lupo.
+
+- If you try to run a non-Python file with coverage.py, you will now get a more
+ useful error message. `Issue 514`_.
+
+- The default pragma regex changed slightly, but this will only matter to you
+ if you are deranged and use mixed-case pragmas.
+
+- Deal properly with non-ASCII file names in an ASCII-only world, `issue 533`_.
+
+- Programs that set Unicode configuration values could cause UnicodeErrors when
+ generating HTML reports. Pytest-cov is one example. This is now fixed.
+
+- Prevented deprecation warnings from configparser that happened in some
+ circumstances, closing `issue 530`_.
+
+- Corrected the name of the jquery.ba-throttle-debounce.js library. Thanks,
+ Ben Finney. Closes `issue 505`_.
+
+- Testing against PyPy 5.6 and PyPy3 5.5.
+
+- Switched to pytest from nose for running the coverage.py tests.
+
+- Renamed AUTHORS.txt to CONTRIBUTORS.txt, since there are other ways to
+ contribute than by writing code. Also put the count of contributors into the
+ author string in setup.py, though this might be too cute.
+
+.. _sys.excepthook: https://docs.python.org/3/library/sys.html#sys.excepthook
+.. _issue 265: https://bitbucket.org/ned/coveragepy/issues/265/when-using-source-include-is-silently
+.. _issue 412: https://bitbucket.org/ned/coveragepy/issues/412/coverage-combine-should-error-if-no
+.. _issue 433: https://bitbucket.org/ned/coveragepy/issues/433/coverage-html-does-not-suport-skip-covered
+.. _issue 493: https://bitbucket.org/ned/coveragepy/issues/493/confusing-branching-failure
+.. _issue 496: https://bitbucket.org/ned/coveragepy/issues/496/incorrect-coverage-with-branching-and
+.. _issue 502: https://bitbucket.org/ned/coveragepy/issues/502/incorrect-coverage-report-with-cover
+.. _issue 505: https://bitbucket.org/ned/coveragepy/issues/505/use-canonical-filename-for-debounce
+.. _issue 514: https://bitbucket.org/ned/coveragepy/issues/514/path-to-problem-file-not-reported-when
+.. _issue 510: https://bitbucket.org/ned/coveragepy/issues/510/erase-still-needed-in-42
+.. _issue 511: https://bitbucket.org/ned/coveragepy/issues/511/version-42-coverage-combine-empties
+.. _issue 516: https://bitbucket.org/ned/coveragepy/issues/516/running-coverage-combine-twice-deletes-all
+.. _issue 519: https://bitbucket.org/ned/coveragepy/issues/519/coverage-run-sections-in-toxini-or-as
+.. _issue 524: https://bitbucket.org/ned/coveragepy/issues/524/coverage-report-with-skip-covered-column
+.. _issue 525: https://bitbucket.org/ned/coveragepy/issues/525/coverage-combine-when-not-in-parallel-mode
+.. _issue 529: https://bitbucket.org/ned/coveragepy/issues/529/encoding-marker-may-only-appear-on-the
+.. _issue 530: https://bitbucket.org/ned/coveragepy/issues/530/deprecationwarning-you-passed-a-bytestring
+.. _issue 533: https://bitbucket.org/ned/coveragepy/issues/533/exception-on-unencodable-file-name
+.. _issue 535: https://bitbucket.org/ned/coveragepy/issues/535/sysexcepthook-is-not-called
+
+
+.. _changes_42:
+
+Version 4.2 --- 2016-07-26
+--------------------------
+
+- Since ``concurrency=multiprocessing`` uses subprocesses, options specified on
+ the coverage.py command line will not be communicated down to them. Only
+ options in the configuration file will apply to the subprocesses.
+ Previously, the options didn't apply to the subprocesses, but there was no
+ indication. Now it is an error to use ``--concurrency=multiprocessing`` and
+ other run-affecting options on the command line. This prevents
+ failures like those reported in `issue 495`_.
+
+- Filtering the HTML report is now faster, thanks to Ville Skyttä.
+
+.. _issue 495: https://bitbucket.org/ned/coveragepy/issues/495/branch-and-concurrency-are-conflicting
+
+
+Version 4.2b1 --- 2016-07-04
+----------------------------
+
+Work from the PyCon 2016 Sprints!
+
+- BACKWARD INCOMPATIBILITY: the ``coverage combine`` command now ignores an
+ existing ``.coverage`` data file. It used to include that file in its
+ combining. This caused confusing results, and extra tox "clean" steps. If
+ you want the old behavior, use the new ``coverage combine --append`` option.
+
+- The ``concurrency`` option can now take multiple values, to support programs
+ using multiprocessing and another library such as eventlet. This is only
+ possible in the configuration file, not from the command line. The
+ configuration file is the only way for sub-processes to all run with the same
+ options. Fixes `issue 484`_. Thanks to Josh Williams for prototyping.
+
+- Using a ``concurrency`` setting of ``multiprocessing`` now implies
+ ``--parallel`` so that the main program is measured similarly to the
+ sub-processes.
+
+- When using `automatic subprocess measurement`_, running coverage commands
+ would create spurious data files. This is now fixed, thanks to diagnosis and
+ testing by Dan Riti. Closes `issue 492`_.
+
+- A new configuration option, ``report:sort``, controls what column of the
+ text report is used to sort the rows. Thanks to Dan Wandschneider, this
+ closes `issue 199`_.
+
+- The HTML report has a more-visible indicator for which column is being
+ sorted. Closes `issue 298`_, thanks to Josh Williams.
+
+- If the HTML report cannot find the source for a file, the message now
+ suggests using the ``-i`` flag to allow the report to continue. Closes
+ `issue 231`_, thanks, Nathan Land.
+
+- When reports are ignoring errors, there's now a warning if a file cannot be
+ parsed, rather than being silently ignored. Closes `issue 396`_. Thanks,
+ Matthew Boehm.
+
+- A new option for ``coverage debug`` is available: ``coverage debug config``
+ shows the current configuration. Closes `issue 454`_, thanks to Matthew
+ Boehm.
+
+- Running coverage as a module (``python -m coverage``) no longer shows the
+ program name as ``__main__.py``. Fixes `issue 478`_. Thanks, Scott Belden.
+
+- The `test_helpers` module has been moved into a separate pip-installable
+ package: `unittest-mixins`_.
+
+.. _automatic subprocess measurement: https://coverage.readthedocs.io/en/latest/subprocess.html
+.. _issue 199: https://bitbucket.org/ned/coveragepy/issues/199/add-a-way-to-sort-the-text-report
+.. _issue 231: https://bitbucket.org/ned/coveragepy/issues/231/various-default-behavior-in-report-phase
+.. _issue 298: https://bitbucket.org/ned/coveragepy/issues/298/show-in-html-report-that-the-columns-are
+.. _issue 396: https://bitbucket.org/ned/coveragepy/issues/396/coverage-xml-shouldnt-bail-out-on-parse
+.. _issue 454: https://bitbucket.org/ned/coveragepy/issues/454/coverage-debug-config-should-be
+.. _issue 478: https://bitbucket.org/ned/coveragepy/issues/478/help-shows-silly-program-name-when-running
+.. _issue 484: https://bitbucket.org/ned/coveragepy/issues/484/multiprocessing-greenlet-concurrency
+.. _issue 492: https://bitbucket.org/ned/coveragepy/issues/492/subprocess-coverage-strange-detection-of
+.. _unittest-mixins: https://pypi.org/project/unittest-mixins/
+
+
+.. _changes_41:
+
+Version 4.1 --- 2016-05-21
+--------------------------
+
+- The internal attribute `Reporter.file_reporters` was removed in 4.1b3. It
+ should have come has no surprise that there were third-party tools out there
+ using that attribute. It has been restored, but with a deprecation warning.
+
+
+Version 4.1b3 --- 2016-05-10
+----------------------------
+
+- When running your program, execution can jump from an ``except X:`` line to
+ some other line when an exception other than ``X`` happens. This jump is no
+ longer considered a branch when measuring branch coverage.
+
+- When measuring branch coverage, ``yield`` statements that were never resumed
+ were incorrectly marked as missing, as reported in `issue 440`_. This is now
+ fixed.
+
+- During branch coverage of single-line callables like lambdas and generator
+ expressions, coverage.py can now distinguish between them never being called,
+ or being called but not completed. Fixes `issue 90`_, `issue 460`_ and
+ `issue 475`_.
+
+- The HTML report now has a map of the file along the rightmost edge of the
+ page, giving an overview of where the missed lines are. Thanks, Dmitry
+ Shishov.
+
+- The HTML report now uses different monospaced fonts, favoring Consolas over
+ Courier. Along the way, `issue 472`_ about not properly handling one-space
+ indents was fixed. The index page also has slightly different styling, to
+ try to make the clickable detail pages more apparent.
+
+- Missing branches reported with ``coverage report -m`` will now say ``->exit``
+ for missed branches to the exit of a function, rather than a negative number.
+ Fixes `issue 469`_.
+
+- ``coverage --help`` and ``coverage --version`` now mention which tracer is
+ installed, to help diagnose problems. The docs mention which features need
+ the C extension. (`issue 479`_)
+
+- Officially support PyPy 5.1, which required no changes, just updates to the
+ docs.
+
+- The `Coverage.report` function had two parameters with non-None defaults,
+ which have been changed. `show_missing` used to default to True, but now
+ defaults to None. If you had been calling `Coverage.report` without
+ specifying `show_missing`, you'll need to explicitly set it to True to keep
+ the same behavior. `skip_covered` used to default to False. It is now None,
+ which doesn't change the behavior. This fixes `issue 485`_.
+
+- It's never been possible to pass a namespace module to one of the analysis
+ functions, but now at least we raise a more specific error message, rather
+ than getting confused. (`issue 456`_)
+
+- The `coverage.process_startup` function now returns the `Coverage` instance
+ it creates, as suggested in `issue 481`_.
+
+- Make a small tweak to how we compare threads, to avoid buggy custom
+ comparison code in thread classes. (`issue 245`_)
+
+.. _issue 90: https://bitbucket.org/ned/coveragepy/issues/90/lambda-expression-confuses-branch
+.. _issue 245: https://bitbucket.org/ned/coveragepy/issues/245/change-solution-for-issue-164
+.. _issue 440: https://bitbucket.org/ned/coveragepy/issues/440/yielded-twisted-failure-marked-as-missed
+.. _issue 456: https://bitbucket.org/ned/coveragepy/issues/456/coverage-breaks-with-implicit-namespaces
+.. _issue 460: https://bitbucket.org/ned/coveragepy/issues/460/confusing-html-report-for-certain-partial
+.. _issue 469: https://bitbucket.org/ned/coveragepy/issues/469/strange-1-line-number-in-branch-coverage
+.. _issue 472: https://bitbucket.org/ned/coveragepy/issues/472/html-report-indents-incorrectly-for-one
+.. _issue 475: https://bitbucket.org/ned/coveragepy/issues/475/generator-expression-is-marked-as-not
+.. _issue 479: https://bitbucket.org/ned/coveragepy/issues/479/clarify-the-need-for-the-c-extension
+.. _issue 481: https://bitbucket.org/ned/coveragepy/issues/481/asyncioprocesspoolexecutor-tracing-not
+.. _issue 485: https://bitbucket.org/ned/coveragepy/issues/485/coveragereport-ignores-show_missing-and
+
+
+Version 4.1b2 --- 2016-01-23
+----------------------------
+
+- Problems with the new branch measurement in 4.1 beta 1 were fixed:
+
+ - Class docstrings were considered executable. Now they no longer are.
+
+ - ``yield from`` and ``await`` were considered returns from functions, since
+ they could transfer control to the caller. This produced unhelpful
+ "missing branch" reports in a number of circumstances. Now they no longer
+ are considered returns.
+
+ - In unusual situations, a missing branch to a negative number was reported.
+ This has been fixed, closing `issue 466`_.
+
+- The XML report now produces correct package names for modules found in
+ directories specified with ``source=``. Fixes `issue 465`_.
+
+- ``coverage report`` won't produce trailing whitespace.
+
+.. _issue 465: https://bitbucket.org/ned/coveragepy/issues/465/coveragexml-produces-package-names-with-an
+.. _issue 466: https://bitbucket.org/ned/coveragepy/issues/466/impossible-missed-branch-to-a-negative
+
+
+Version 4.1b1 --- 2016-01-10
+----------------------------
+
+- Branch analysis has been rewritten: it used to be based on bytecode, but now
+ uses AST analysis. This has changed a number of things:
+
+ - More code paths are now considered runnable, especially in
+ ``try``/``except`` structures. This may mean that coverage.py will
+ identify more code paths as uncovered. This could either raise or lower
+ your overall coverage number.
+
+ - Python 3.5's ``async`` and ``await`` keywords are properly supported,
+ fixing `issue 434`_.
+
+ - Some long-standing branch coverage bugs were fixed:
+
+ - `issue 129`_: functions with only a docstring for a body would
+ incorrectly report a missing branch on the ``def`` line.
+
+ - `issue 212`_: code in an ``except`` block could be incorrectly marked as
+ a missing branch.
+
+ - `issue 146`_: context managers (``with`` statements) in a loop or ``try``
+ block could confuse the branch measurement, reporting incorrect partial
+ branches.
+
+ - `issue 422`_: in Python 3.5, an actual partial branch could be marked as
+ complete.
+
+- Pragmas to disable coverage measurement can now be used on decorator lines,
+ and they will apply to the entire function or class being decorated. This
+ implements the feature requested in `issue 131`_.
+
+- Multiprocessing support is now available on Windows. Thanks, Rodrigue
+ Cloutier.
+
+- Files with two encoding declarations are properly supported, fixing
+ `issue 453`_. Thanks, Max Linke.
+
+- Non-ascii characters in regexes in the configuration file worked in 3.7, but
+ stopped working in 4.0. Now they work again, closing `issue 455`_.
+
+- Form-feed characters would prevent accurate determination of the beginning of
+ statements in the rest of the file. This is now fixed, closing `issue 461`_.
+
+.. _issue 129: https://bitbucket.org/ned/coveragepy/issues/129/misleading-branch-coverage-of-empty
+.. _issue 131: https://bitbucket.org/ned/coveragepy/issues/131/pragma-on-a-decorator-line-should-affect
+.. _issue 146: https://bitbucket.org/ned/coveragepy/issues/146/context-managers-confuse-branch-coverage
+.. _issue 212: https://bitbucket.org/ned/coveragepy/issues/212/coverage-erroneously-reports-partial
+.. _issue 422: https://bitbucket.org/ned/coveragepy/issues/422/python35-partial-branch-marked-as-fully
+.. _issue 434: https://bitbucket.org/ned/coveragepy/issues/434/indexerror-in-python-35
+.. _issue 453: https://bitbucket.org/ned/coveragepy/issues/453/source-code-encoding-can-only-be-specified
+.. _issue 455: https://bitbucket.org/ned/coveragepy/issues/455/unusual-exclusions-stopped-working-in
+.. _issue 461: https://bitbucket.org/ned/coveragepy/issues/461/multiline-asserts-need-too-many-pragma
+
+
+.. _changes_403:
+
+Version 4.0.3 --- 2015-11-24
+----------------------------
+
+- Fixed a mysterious problem that manifested in different ways: sometimes
+ hanging the process (`issue 420`_), sometimes making database connections
+ fail (`issue 445`_).
+
+- The XML report now has correct ``<source>`` elements when using a
+ ``--source=`` option somewhere besides the current directory. This fixes
+ `issue 439`_. Thanks, Arcadiy Ivanov.
+
+- Fixed an unusual edge case of detecting source encodings, described in
+ `issue 443`_.
+
+- Help messages that mention the command to use now properly use the actual
+ command name, which might be different than "coverage". Thanks to Ben
+ Finney, this closes `issue 438`_.
+
+.. _issue 420: https://bitbucket.org/ned/coveragepy/issues/420/coverage-40-hangs-indefinitely-on-python27
+.. _issue 438: https://bitbucket.org/ned/coveragepy/issues/438/parameterise-coverage-command-name
+.. _issue 439: https://bitbucket.org/ned/coveragepy/issues/439/incorrect-cobertura-file-sources-generated
+.. _issue 443: https://bitbucket.org/ned/coveragepy/issues/443/coverage-gets-confused-when-encoding
+.. _issue 445: https://bitbucket.org/ned/coveragepy/issues/445/django-app-cannot-connect-to-cassandra
+
+
+.. _changes_402:
+
+Version 4.0.2 --- 2015-11-04
+----------------------------
+
+- More work on supporting unusually encoded source. Fixed `issue 431`_.
+
+- Files or directories with non-ASCII characters are now handled properly,
+ fixing `issue 432`_.
+
+- Setting a trace function with sys.settrace was broken by a change in 4.0.1,
+ as reported in `issue 436`_. This is now fixed.
+
+- Officially support PyPy 4.0, which required no changes, just updates to the
+ docs.
+
+.. _issue 431: https://bitbucket.org/ned/coveragepy/issues/431/couldnt-parse-python-file-with-cp1252
+.. _issue 432: https://bitbucket.org/ned/coveragepy/issues/432/path-with-unicode-characters-various
+.. _issue 436: https://bitbucket.org/ned/coveragepy/issues/436/disabled-coverage-ctracer-may-rise-from
+
+
+.. _changes_401:
+
+Version 4.0.1 --- 2015-10-13
+----------------------------
+
+- When combining data files, unreadable files will now generate a warning
+ instead of failing the command. This is more in line with the older
+ coverage.py v3.7.1 behavior, which silently ignored unreadable files.
+ Prompted by `issue 418`_.
+
+- The --skip-covered option would skip reporting on 100% covered files, but
+ also skipped them when calculating total coverage. This was wrong, it should
+ only remove lines from the report, not change the final answer. This is now
+ fixed, closing `issue 423`_.
+
+- In 4.0, the data file recorded a summary of the system on which it was run.
+ Combined data files would keep all of those summaries. This could lead to
+ enormous data files consisting of mostly repetitive useless information. That
+ summary is now gone, fixing `issue 415`_. If you want summary information,
+ get in touch, and we'll figure out a better way to do it.
+
+- Test suites that mocked os.path.exists would experience strange failures, due
+ to coverage.py using their mock inadvertently. This is now fixed, closing
+ `issue 416`_.
+
+- Importing a ``__init__`` module explicitly would lead to an error:
+ ``AttributeError: 'module' object has no attribute '__path__'``, as reported
+ in `issue 410`_. This is now fixed.
+
+- Code that uses ``sys.settrace(sys.gettrace())`` used to incur a more than 2x
+ speed penalty. Now there's no penalty at all. Fixes `issue 397`_.
+
+- Pyexpat C code will no longer be recorded as a source file, fixing
+ `issue 419`_.
+
+- The source kit now contains all of the files needed to have a complete source
+ tree, re-fixing `issue 137`_ and closing `issue 281`_.
+
+.. _issue 281: https://bitbucket.org/ned/coveragepy/issues/281/supply-scripts-for-testing-in-the
+.. _issue 397: https://bitbucket.org/ned/coveragepy/issues/397/stopping-and-resuming-coverage-with
+.. _issue 410: https://bitbucket.org/ned/coveragepy/issues/410/attributeerror-module-object-has-no
+.. _issue 415: https://bitbucket.org/ned/coveragepy/issues/415/repeated-coveragedataupdates-cause
+.. _issue 416: https://bitbucket.org/ned/coveragepy/issues/416/mocking-ospathexists-causes-failures
+.. _issue 418: https://bitbucket.org/ned/coveragepy/issues/418/json-parse-error
+.. _issue 419: https://bitbucket.org/ned/coveragepy/issues/419/nosource-no-source-for-code-path-to-c
+.. _issue 423: https://bitbucket.org/ned/coveragepy/issues/423/skip_covered-changes-reported-total
+
+
+.. _changes_40:
+
+Version 4.0 --- 2015-09-20
+--------------------------
+
+No changes from 4.0b3
+
+
+Version 4.0b3 --- 2015-09-07
+----------------------------
+
+- Reporting on an unmeasured file would fail with a traceback. This is now
+ fixed, closing `issue 403`_.
+
+- The Jenkins ShiningPanda_ plugin looks for an obsolete file name to find the
+ HTML reports to publish, so it was failing under coverage.py 4.0. Now we
+ create that file if we are running under Jenkins, to keep things working
+ smoothly. `issue 404`_.
+
+- Kits used to include tests and docs, but didn't install them anywhere, or
+ provide all of the supporting tools to make them useful. Kits no longer
+ include tests and docs. If you were using them from the older packages, get
+ in touch and help me understand how.
+
+.. _issue 403: https://bitbucket.org/ned/coveragepy/issues/403/hasherupdate-fails-with-typeerror-nonetype
+.. _issue 404: https://bitbucket.org/ned/coveragepy/issues/404/shiningpanda-jenkins-plugin-cant-find-html
+
+
+Version 4.0b2 --- 2015-08-22
+----------------------------
+
+- 4.0b1 broke ``--append`` creating new data files. This is now fixed, closing
+ `issue 392`_.
+
+- ``py.test --cov`` can write empty data, then touch files due to ``--source``,
+ which made coverage.py mistakenly force the data file to record lines instead
+ of arcs. This would lead to a "Can't combine line data with arc data" error
+ message. This is now fixed, and changed some method names in the
+ CoverageData interface. Fixes `issue 399`_.
+
+- `CoverageData.read_fileobj` and `CoverageData.write_fileobj` replace the
+ `.read` and `.write` methods, and are now properly inverses of each other.
+
+- When using ``report --skip-covered``, a message will now be included in the
+ report output indicating how many files were skipped, and if all files are
+ skipped, coverage.py won't accidentally scold you for having no data to
+ report. Thanks, Krystian Kichewko.
+
+- A new conversion utility has been added: ``python -m coverage.pickle2json``
+ will convert v3.x pickle data files to v4.x JSON data files. Thanks,
+ Alexander Todorov. Closes `issue 395`_.
+
+- A new version identifier is available, `coverage.version_info`, a plain tuple
+ of values similar to `sys.version_info`_.
+
+.. _issue 392: https://bitbucket.org/ned/coveragepy/issues/392/run-append-doesnt-create-coverage-file
+.. _issue 395: https://bitbucket.org/ned/coveragepy/issues/395/rfe-read-pickled-files-as-well-for
+.. _issue 399: https://bitbucket.org/ned/coveragepy/issues/399/coverageexception-cant-combine-line-data
+.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info
+
+
+Version 4.0b1 --- 2015-08-02
+----------------------------
+
+- Coverage.py is now licensed under the Apache 2.0 license. See NOTICE.txt for
+ details. Closes `issue 313`_.
+
+- The data storage has been completely revamped. The data file is now
+ JSON-based instead of a pickle, closing `issue 236`_. The `CoverageData`
+ class is now a public supported documented API to the data file.
+
+- A new configuration option, ``[run] note``, lets you set a note that will be
+ stored in the `runs` section of the data file. You can use this to annotate
+ the data file with any information you like.
+
+- Unrecognized configuration options will now print an error message and stop
+ coverage.py. This should help prevent configuration mistakes from passing
+ silently. Finishes `issue 386`_.
+
+- In parallel mode, ``coverage erase`` will now delete all of the data files,
+ fixing `issue 262`_.
+
+- Coverage.py now accepts a directory name for ``coverage run`` and will run a
+ ``__main__.py`` found there, just like Python will. Fixes `issue 252`_.
+ Thanks, Dmitry Trofimov.
+
+- The XML report now includes a ``missing-branches`` attribute. Thanks, Steve
+ Peak. This is not a part of the Cobertura DTD, so the XML report no longer
+ references the DTD.
+
+- Missing branches in the HTML report now have a bit more information in the
+ right-hand annotations. Hopefully this will make their meaning clearer.
+
+- All the reporting functions now behave the same if no data had been
+ collected, exiting with a status code of 1. Fixed ``fail_under`` to be
+ applied even when the report is empty. Thanks, Ionel Cristian Mărieș.
+
+- Plugins are now initialized differently. Instead of looking for a class
+ called ``Plugin``, coverage.py looks for a function called ``coverage_init``.
+
+- A file-tracing plugin can now ask to have built-in Python reporting by
+ returning `"python"` from its `file_reporter()` method.
+
+- Code that was executed with `exec` would be mis-attributed to the file that
+ called it. This is now fixed, closing `issue 380`_.
+
+- The ability to use item access on `Coverage.config` (introduced in 4.0a2) has
+ been changed to a more explicit `Coverage.get_option` and
+ `Coverage.set_option` API.
+
+- The ``Coverage.use_cache`` method is no longer supported.
+
+- The private method ``Coverage._harvest_data`` is now called
+ ``Coverage.get_data``, and returns the ``CoverageData`` containing the
+ collected data.
+
+- The project is consistently referred to as "coverage.py" throughout the code
+ and the documentation, closing `issue 275`_.
+
+- Combining data files with an explicit configuration file was broken in 4.0a6,
+ but now works again, closing `issue 385`_.
+
+- ``coverage combine`` now accepts files as well as directories.
+
+- The speed is back to 3.7.1 levels, after having slowed down due to plugin
+ support, finishing up `issue 387`_.
+
+.. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad
+.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with
+.. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all
+.. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage
+.. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4
+.. _issue 380: https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from
+.. _issue 385: https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile
+.. _issue 386: https://bitbucket.org/ned/coveragepy/issues/386/error-on-unrecognised-configuration
+.. _issue 387: https://bitbucket.org/ned/coveragepy/issues/387/performance-degradation-from-371-to-40
+
+.. 40 issues closed in 4.0 below here
+
+
+Version 4.0a6 --- 2015-06-21
+----------------------------
+
+- Python 3.5b2 and PyPy 2.6.0 are supported.
+
+- The original module-level function interface to coverage.py is no longer
+ supported. You must now create a ``coverage.Coverage`` object, and use
+ methods on it.
+
+- The ``coverage combine`` command now accepts any number of directories as
+ arguments, and will combine all the data files from those directories. This
+ means you don't have to copy the files to one directory before combining.
+ Thanks, Christine Lytwynec. Finishes `issue 354`_.
+
+- Branch coverage couldn't properly handle certain extremely long files. This
+ is now fixed (`issue 359`_).
+
+- Branch coverage didn't understand yield statements properly. Mickie Betz
+ persisted in pursuing this despite Ned's pessimism. Fixes `issue 308`_ and
+ `issue 324`_.
+
+- The COVERAGE_DEBUG environment variable can be used to set the
+ ``[run] debug`` configuration option to control what internal operations are
+ logged.
+
+- HTML reports were truncated at formfeed characters. This is now fixed
+ (`issue 360`_). It's always fun when the problem is due to a `bug in the
+ Python standard library <http://bugs.python.org/issue19035>`_.
+
+- Files with incorrect encoding declaration comments are no longer ignored by
+ the reporting commands, fixing `issue 351`_.
+
+- HTML reports now include a timestamp in the footer, closing `issue 299`_.
+ Thanks, Conrad Ho.
+
+- HTML reports now begrudgingly use double-quotes rather than single quotes,
+ because there are "software engineers" out there writing tools that read HTML
+ and somehow have no idea that single quotes exist. Capitulates to the absurd
+ `issue 361`_. Thanks, Jon Chappell.
+
+- The ``coverage annotate`` command now handles non-ASCII characters properly,
+ closing `issue 363`_. Thanks, Leonardo Pistone.
+
+- Drive letters on Windows were not normalized correctly, now they are. Thanks,
+ Ionel Cristian Mărieș.
+
+- Plugin support had some bugs fixed, closing `issue 374`_ and `issue 375`_.
+ Thanks, Stefan Behnel.
+
+.. _issue 299: https://bitbucket.org/ned/coveragepy/issues/299/inserted-created-on-yyyy-mm-dd-hh-mm-in
+.. _issue 308: https://bitbucket.org/ned/coveragepy/issues/308/yield-lambda-branch-coverage
+.. _issue 324: https://bitbucket.org/ned/coveragepy/issues/324/yield-in-loop-confuses-branch-coverage
+.. _issue 351: https://bitbucket.org/ned/coveragepy/issues/351/files-with-incorrect-encoding-are-ignored
+.. _issue 354: https://bitbucket.org/ned/coveragepy/issues/354/coverage-combine-should-take-a-list-of
+.. _issue 359: https://bitbucket.org/ned/coveragepy/issues/359/xml-report-chunk-error
+.. _issue 360: https://bitbucket.org/ned/coveragepy/issues/360/html-reports-get-confused-by-l-in-the-code
+.. _issue 361: https://bitbucket.org/ned/coveragepy/issues/361/use-double-quotes-in-html-output-to
+.. _issue 363: https://bitbucket.org/ned/coveragepy/issues/363/annotate-command-hits-unicode-happy-fun
+.. _issue 374: https://bitbucket.org/ned/coveragepy/issues/374/c-tracer-lookups-fail-in
+.. _issue 375: https://bitbucket.org/ned/coveragepy/issues/375/ctracer_handle_return-reads-byte-code
+
+
+Version 4.0a5 --- 2015-02-16
+----------------------------
+
+- Plugin support is now implemented in the C tracer instead of the Python
+ tracer. This greatly improves the speed of tracing projects using plugins.
+
+- Coverage.py now always adds the current directory to sys.path, so that
+ plugins can import files in the current directory (`issue 358`_).
+
+- If the `config_file` argument to the Coverage constructor is specified as
+ ".coveragerc", it is treated as if it were True. This means setup.cfg is
+ also examined, and a missing file is not considered an error (`issue 357`_).
+
+- Wildly experimental: support for measuring processes started by the
+ multiprocessing module. To use, set ``--concurrency=multiprocessing``,
+ either on the command line or in the .coveragerc file (`issue 117`_). Thanks,
+ Eduardo Schettino. Currently, this does not work on Windows.
+
+- A new warning is possible, if a desired file isn't measured because it was
+ imported before coverage.py was started (`issue 353`_).
+
+- The `coverage.process_startup` function now will start coverage measurement
+ only once, no matter how many times it is called. This fixes problems due
+ to unusual virtualenv configurations (`issue 340`_).
+
+- Added 3.5.0a1 to the list of supported CPython versions.
+
+.. _issue 117: https://bitbucket.org/ned/coveragepy/issues/117/enable-coverage-measurement-of-code-run-by
+.. _issue 340: https://bitbucket.org/ned/coveragepy/issues/340/keyerror-subpy
+.. _issue 353: https://bitbucket.org/ned/coveragepy/issues/353/40a3-introduces-an-unexpected-third-case
+.. _issue 357: https://bitbucket.org/ned/coveragepy/issues/357/behavior-changed-when-coveragerc-is
+.. _issue 358: https://bitbucket.org/ned/coveragepy/issues/358/all-coverage-commands-should-adjust
+
+
+Version 4.0a4 --- 2015-01-25
+----------------------------
+
+- Plugins can now provide sys_info for debugging output.
+
+- Started plugins documentation.
+
+- Prepared to move the docs to readthedocs.org.
+
+
+Version 4.0a3 --- 2015-01-20
+----------------------------
+
+- Reports now use file names with extensions. Previously, a report would
+ describe a/b/c.py as "a/b/c". Now it is shown as "a/b/c.py". This allows
+ for better support of non-Python files, and also fixed `issue 69`_.
+
+- The XML report now reports each directory as a package again. This was a bad
+ regression, I apologize. This was reported in `issue 235`_, which is now
+ fixed.
+
+- A new configuration option for the XML report: ``[xml] package_depth``
+ controls which directories are identified as packages in the report.
+ Directories deeper than this depth are not reported as packages.
+ The default is that all directories are reported as packages.
+ Thanks, Lex Berezhny.
+
+- When looking for the source for a frame, check if the file exists. On
+ Windows, .pyw files are no longer recorded as .py files. Along the way, this
+ fixed `issue 290`_.
+
+- Empty files are now reported as 100% covered in the XML report, not 0%
+ covered (`issue 345`_).
+
+- Regexes in the configuration file are now compiled as soon as they are read,
+ to provide error messages earlier (`issue 349`_).
+
+.. _issue 69: https://bitbucket.org/ned/coveragepy/issues/69/coverage-html-overwrite-files-that-doesnt
+.. _issue 235: https://bitbucket.org/ned/coveragepy/issues/235/package-name-is-missing-in-xml-report
+.. _issue 290: https://bitbucket.org/ned/coveragepy/issues/290/running-programmatically-with-pyw-files
+.. _issue 345: https://bitbucket.org/ned/coveragepy/issues/345/xml-reports-line-rate-0-for-empty-files
+.. _issue 349: https://bitbucket.org/ned/coveragepy/issues/349/bad-regex-in-config-should-get-an-earlier
+
+
+Version 4.0a2 --- 2015-01-14
+----------------------------
+
+- Officially support PyPy 2.4, and PyPy3 2.4. Drop support for
+ CPython 3.2 and older versions of PyPy. The code won't work on CPython 3.2.
+ It will probably still work on older versions of PyPy, but I'm not testing
+ against them.
+
+- Plugins!
+
+- The original command line switches (`-x` to run a program, etc) are no
+ longer supported.
+
+- A new option: `coverage report --skip-covered` will reduce the number of
+ files reported by skipping files with 100% coverage. Thanks, Krystian
+ Kichewko. This means that empty `__init__.py` files will be skipped, since
+ they are 100% covered, closing `issue 315`_.
+
+- You can now specify the ``--fail-under`` option in the ``.coveragerc`` file
+ as the ``[report] fail_under`` option. This closes `issue 314`_.
+
+- The ``COVERAGE_OPTIONS`` environment variable is no longer supported. It was
+ a hack for ``--timid`` before configuration files were available.
+
+- The HTML report now has filtering. Type text into the Filter box on the
+ index page, and only modules with that text in the name will be shown.
+ Thanks, Danny Allen.
+
+- The textual report and the HTML report used to report partial branches
+ differently for no good reason. Now the text report's "missing branches"
+ column is a "partial branches" column so that both reports show the same
+ numbers. This closes `issue 342`_.
+
+- If you specify a ``--rcfile`` that cannot be read, you will get an error
+ message. Fixes `issue 343`_.
+
+- The ``--debug`` switch can now be used on any command.
+
+- You can now programmatically adjust the configuration of coverage.py by
+ setting items on `Coverage.config` after construction.
+
+- A module run with ``-m`` can be used as the argument to ``--source``, fixing
+ `issue 328`_. Thanks, Buck Evan.
+
+- The regex for matching exclusion pragmas has been fixed to allow more kinds
+ of whitespace, fixing `issue 334`_.
+
+- Made some PyPy-specific tweaks to improve speed under PyPy. Thanks, Alex
+ Gaynor.
+
+- In some cases, with a source file missing a final newline, coverage.py would
+ count statements incorrectly. This is now fixed, closing `issue 293`_.
+
+- The status.dat file that HTML reports use to avoid re-creating files that
+ haven't changed is now a JSON file instead of a pickle file. This obviates
+ `issue 287`_ and `issue 237`_.
+
+.. _issue 237: https://bitbucket.org/ned/coveragepy/issues/237/htmlcov-with-corrupt-statusdat
+.. _issue 287: https://bitbucket.org/ned/coveragepy/issues/287/htmlpy-doesnt-specify-pickle-protocol
+.. _issue 293: https://bitbucket.org/ned/coveragepy/issues/293/number-of-statement-detection-wrong-if-no
+.. _issue 314: https://bitbucket.org/ned/coveragepy/issues/314/fail_under-param-not-working-in-coveragerc
+.. _issue 315: https://bitbucket.org/ned/coveragepy/issues/315/option-to-omit-empty-files-eg-__init__py
+.. _issue 328: https://bitbucket.org/ned/coveragepy/issues/328/misbehavior-in-run-source
+.. _issue 334: https://bitbucket.org/ned/coveragepy/issues/334/pragma-not-recognized-if-tab-character
+.. _issue 342: https://bitbucket.org/ned/coveragepy/issues/342/console-and-html-coverage-reports-differ
+.. _issue 343: https://bitbucket.org/ned/coveragepy/issues/343/an-explicitly-named-non-existent-config
+
+
+Version 4.0a1 --- 2014-09-27
+----------------------------
+
+- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and
+ PyPy 2.2.
+
+- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_.
+ The ``concurrency`` setting specifies the concurrency library in use. Huge
+ thanks to Peter Portante for initial implementation, and to Joe Jevnik for
+ the final insight that completed the work.
+
+- Options are now also read from a setup.cfg file, if any. Sections are
+ prefixed with "coverage:", so the ``[run]`` options will be read from the
+ ``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_.
+
+- The ``report -m`` command can now show missing branches when reporting on
+ branch coverage. Thanks, Steve Leonard. Closes `issue 230`_.
+
+- The XML report now contains a <source> element, fixing `issue 94`_. Thanks
+ Stan Hu.
+
+- The class defined in the coverage module is now called ``Coverage`` instead
+ of ``coverage``, though the old name still works, for backward compatibility.
+
+- The ``fail-under`` value is now rounded the same as reported results,
+ preventing paradoxical results, fixing `issue 284`_.
+
+- The XML report will now create the output directory if need be, fixing
+ `issue 285`_. Thanks, Chris Rose.
+
+- HTML reports no longer raise UnicodeDecodeError if a Python file has
+ undecodable characters, fixing `issue 303`_ and `issue 331`_.
+
+- The annotate command will now annotate all files, not just ones relative to
+ the current directory, fixing `issue 57`_.
+
+- The coverage module no longer causes deprecation warnings on Python 3.4 by
+ importing the imp module, fixing `issue 305`_.
+
+- Encoding declarations in source files are only considered if they are truly
+ comments. Thanks, Anthony Sottile.
+
+.. _issue 57: https://bitbucket.org/ned/coveragepy/issues/57/annotate-command-fails-to-annotate-many
+.. _issue 94: https://bitbucket.org/ned/coveragepy/issues/94/coverage-xml-doesnt-produce-sources
+.. _issue 149: https://bitbucket.org/ned/coveragepy/issues/149/coverage-gevent-looks-broken
+.. _issue 230: https://bitbucket.org/ned/coveragepy/issues/230/show-line-no-for-missing-branches-in
+.. _issue 284: https://bitbucket.org/ned/coveragepy/issues/284/fail-under-should-show-more-precision
+.. _issue 285: https://bitbucket.org/ned/coveragepy/issues/285/xml-report-fails-if-output-file-directory
+.. _issue 303: https://bitbucket.org/ned/coveragepy/issues/303/unicodedecodeerror
+.. _issue 304: https://bitbucket.org/ned/coveragepy/issues/304/attempt-to-get-configuration-from-setupcfg
+.. _issue 305: https://bitbucket.org/ned/coveragepy/issues/305/pendingdeprecationwarning-the-imp-module
+.. _issue 331: https://bitbucket.org/ned/coveragepy/issues/331/failure-of-encoding-detection-on-python2
+
+
+.. _changes_371:
+
+Version 3.7.1 --- 2013-12-13
+----------------------------
+
+- Improved the speed of HTML report generation by about 20%.
+
+- Fixed the mechanism for finding OS-installed static files for the HTML report
+ so that it will actually find OS-installed static files.
+
+
+.. _changes_37:
+
+Version 3.7 --- 2013-10-06
+--------------------------
+
+- Added the ``--debug`` switch to ``coverage run``. It accepts a list of
+ options indicating the type of internal activity to log to stderr.
+
+- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_.
+
+- Running code with ``coverage run -m`` now behaves more like Python does,
+ setting sys.path properly, which fixes `issue 207`_ and `issue 242`_.
+
+- Coverage.py can now run .pyc files directly, closing `issue 264`_.
+
+- Coverage.py properly supports .pyw files, fixing `issue 261`_.
+
+- Omitting files within a tree specified with the ``source`` option would
+ cause them to be incorrectly marked as unexecuted, as described in
+ `issue 218`_. This is now fixed.
+
+- When specifying paths to alias together during data combining, you can now
+ specify relative paths, fixing `issue 267`_.
+
+- Most file paths can now be specified with username expansion (``~/src``, or
+ ``~build/src``, for example), and with environment variable expansion
+ (``build/$BUILDNUM/src``).
+
+- Trying to create an XML report with no files to report on, would cause a
+ ZeroDivideError, but no longer does, fixing `issue 250`_.
+
+- When running a threaded program under the Python tracer, coverage.py no
+ longer issues a spurious warning about the trace function changing: "Trace
+ function changed, measurement is likely wrong: None." This fixes `issue
+ 164`_.
+
+- Static files necessary for HTML reports are found in system-installed places,
+ to ease OS-level packaging of coverage.py. Closes `issue 259`_.
+
+- Source files with encoding declarations, but a blank first line, were not
+ decoded properly. Now they are. Thanks, Roger Hu.
+
+- The source kit now includes the ``__main__.py`` file in the root coverage
+ directory, fixing `issue 255`_.
+
+.. _issue 92: https://bitbucket.org/ned/coveragepy/issues/92/finally-clauses-arent-treated-properly-in
+.. _issue 164: https://bitbucket.org/ned/coveragepy/issues/164/trace-function-changed-warning-when-using
+.. _issue 175: https://bitbucket.org/ned/coveragepy/issues/175/branch-coverage-gets-confused-in-certain
+.. _issue 207: https://bitbucket.org/ned/coveragepy/issues/207/run-m-cannot-find-module-or-package-in
+.. _issue 242: https://bitbucket.org/ned/coveragepy/issues/242/running-a-two-level-package-doesnt-work
+.. _issue 218: https://bitbucket.org/ned/coveragepy/issues/218/run-command-does-not-respect-the-omit-flag
+.. _issue 250: https://bitbucket.org/ned/coveragepy/issues/250/uncaught-zerodivisionerror-when-generating
+.. _issue 255: https://bitbucket.org/ned/coveragepy/issues/255/directory-level-__main__py-not-included-in
+.. _issue 259: https://bitbucket.org/ned/coveragepy/issues/259/allow-use-of-system-installed-third-party
+.. _issue 261: https://bitbucket.org/ned/coveragepy/issues/261/pyw-files-arent-reported-properly
+.. _issue 264: https://bitbucket.org/ned/coveragepy/issues/264/coverage-wont-run-pyc-files
+.. _issue 267: https://bitbucket.org/ned/coveragepy/issues/267/relative-path-aliases-dont-work
+
+
+.. _changes_36:
+
+Version 3.6 --- 2013-01-05
+--------------------------
+
+- Added a page to the docs about troublesome situations, closing `issue 226`_,
+ and added some info to the TODO file, closing `issue 227`_.
+
+.. _issue 226: https://bitbucket.org/ned/coveragepy/issues/226/make-readme-section-to-describe-when
+.. _issue 227: https://bitbucket.org/ned/coveragepy/issues/227/update-todo
+
+
+Version 3.6b3 --- 2012-12-29
+----------------------------
+
+- Beta 2 broke the nose plugin. It's fixed again, closing `issue 224`_.
+
+.. _issue 224: https://bitbucket.org/ned/coveragepy/issues/224/36b2-breaks-nosexcover
+
+
+Version 3.6b2 --- 2012-12-23
+----------------------------
+
+- Coverage.py runs on Python 2.3 and 2.4 again. It was broken in 3.6b1.
+
+- The C extension is optionally compiled using a different more widely-used
+ technique, taking another stab at fixing `issue 80`_ once and for all.
+
+- Combining data files would create entries for phantom files if used with
+ ``source`` and path aliases. It no longer does.
+
+- ``debug sys`` now shows the configuration file path that was read.
+
+- If an oddly-behaved package claims that code came from an empty-string
+ file name, coverage.py no longer associates it with the directory name,
+ fixing `issue 221`_.
+
+.. _issue 221: https://bitbucket.org/ned/coveragepy/issues/221/coveragepy-incompatible-with-pyratemp
+
+
+Version 3.6b1 --- 2012-11-28
+----------------------------
+
+- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly
+ in reporting functions, though they were when running. Now they are handled
+ uniformly, closing `issue 143`_ and `issue 163`_. **NOTE**: it is possible
+ that your configurations may now be incorrect. If you use ``include`` or
+ ``omit`` during reporting, whether on the command line, through the API, or
+ in a configuration file, please check carefully that you were not relying on
+ the old broken behavior.
+
+- The **report**, **html**, and **xml** commands now accept a ``--fail-under``
+ switch that indicates in the exit status whether the coverage percentage was
+ less than a particular value. Closes `issue 139`_.
+
+- The reporting functions coverage.report(), coverage.html_report(), and
+ coverage.xml_report() now all return a float, the total percentage covered
+ measurement.
+
+- The HTML report's title can now be set in the configuration file, with the
+ ``--title`` switch on the command line, or via the API.
+
+- Configuration files now support substitution of environment variables, using
+ syntax like ``${WORD}``. Closes `issue 97`_.
+
+- Embarrassingly, the ``[xml] output=`` setting in the .coveragerc file simply
+ didn't work. Now it does.
+
+- The XML report now consistently uses file names for the file name attribute,
+ rather than sometimes using module names. Fixes `issue 67`_.
+ Thanks, Marcus Cobden.
+
+- Coverage percentage metrics are now computed slightly differently under
+ branch coverage. This means that completely unexecuted files will now
+ correctly have 0% coverage, fixing `issue 156`_. This also means that your
+ total coverage numbers will generally now be lower if you are measuring
+ branch coverage.
+
+- When installing, now in addition to creating a "coverage" command, two new
+ aliases are also installed. A "coverage2" or "coverage3" command will be
+ created, depending on whether you are installing in Python 2.x or 3.x.
+ A "coverage-X.Y" command will also be created corresponding to your specific
+ version of Python. Closes `issue 111`_.
+
+- The coverage.py installer no longer tries to bootstrap setuptools or
+ Distribute. You must have one of them installed first, as `issue 202`_
+ recommended.
+
+- The coverage.py kit now includes docs (closing `issue 137`_) and tests.
+
+- On Windows, files are now reported in their correct case, fixing `issue 89`_
+ and `issue 203`_.
+
+- If a file is missing during reporting, the path shown in the error message
+ is now correct, rather than an incorrect path in the current directory.
+ Fixes `issue 60`_.
+
+- Running an HTML report in Python 3 in the same directory as an old Python 2
+ HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_)
+ is now fixed.
+
+- Fixed yet another error trying to parse non-Python files as Python, this
+ time an IndentationError, closing `issue 82`_ for the fourth time...
+
+- If `coverage xml` fails because there is no data to report, it used to
+ create a zero-length XML file. Now it doesn't, fixing `issue 210`_.
+
+- Jython files now work with the ``--source`` option, fixing `issue 100`_.
+
+- Running coverage.py under a debugger is unlikely to work, but it shouldn't
+ fail with "TypeError: 'NoneType' object is not iterable". Fixes `issue
+ 201`_.
+
+- On some Linux distributions, when installed with the OS package manager,
+ coverage.py would report its own code as part of the results. Now it won't,
+ fixing `issue 214`_, though this will take some time to be repackaged by the
+ operating systems.
+
+- Docstrings for the legacy singleton methods are more helpful. Thanks Marius
+ Gedminas. Closes `issue 205`_.
+
+- The pydoc tool can now show documentation for the class `coverage.coverage`.
+ Closes `issue 206`_.
+
+- Added a page to the docs about contributing to coverage.py, closing
+ `issue 171`_.
+
+- When coverage.py ended unsuccessfully, it may have reported odd errors like
+ ``'NoneType' object has no attribute 'isabs'``. It no longer does,
+ so kiss `issue 153`_ goodbye.
+
+.. _issue 60: https://bitbucket.org/ned/coveragepy/issues/60/incorrect-path-to-orphaned-pyc-files
+.. _issue 67: https://bitbucket.org/ned/coveragepy/issues/67/xml-report-filenames-may-be-generated
+.. _issue 89: https://bitbucket.org/ned/coveragepy/issues/89/on-windows-all-packages-are-reported-in
+.. _issue 97: https://bitbucket.org/ned/coveragepy/issues/97/allow-environment-variables-to-be
+.. _issue 100: https://bitbucket.org/ned/coveragepy/issues/100/source-directive-doesnt-work-for-packages
+.. _issue 111: https://bitbucket.org/ned/coveragepy/issues/111/when-installing-coverage-with-pip-not
+.. _issue 137: https://bitbucket.org/ned/coveragepy/issues/137/provide-docs-with-source-distribution
+.. _issue 139: https://bitbucket.org/ned/coveragepy/issues/139/easy-check-for-a-certain-coverage-in-tests
+.. _issue 143: https://bitbucket.org/ned/coveragepy/issues/143/omit-doesnt-seem-to-work-in-coverage
+.. _issue 153: https://bitbucket.org/ned/coveragepy/issues/153/non-existent-filename-triggers
+.. _issue 156: https://bitbucket.org/ned/coveragepy/issues/156/a-completely-unexecuted-file-shows-14
+.. _issue 163: https://bitbucket.org/ned/coveragepy/issues/163/problem-with-include-and-omit-filename
+.. _issue 171: https://bitbucket.org/ned/coveragepy/issues/171/how-to-contribute-and-run-tests
+.. _issue 193: https://bitbucket.org/ned/coveragepy/issues/193/unicodedecodeerror-on-htmlpy
+.. _issue 201: https://bitbucket.org/ned/coveragepy/issues/201/coverage-using-django-14-with-pydb-on
+.. _issue 202: https://bitbucket.org/ned/coveragepy/issues/202/get-rid-of-ez_setuppy-and
+.. _issue 203: https://bitbucket.org/ned/coveragepy/issues/203/duplicate-filenames-reported-when-filename
+.. _issue 205: https://bitbucket.org/ned/coveragepy/issues/205/make-pydoc-coverage-more-friendly
+.. _issue 206: https://bitbucket.org/ned/coveragepy/issues/206/pydoc-coveragecoverage-fails-with-an-error
+.. _issue 210: https://bitbucket.org/ned/coveragepy/issues/210/if-theres-no-coverage-data-coverage-xml
+.. _issue 214: https://bitbucket.org/ned/coveragepy/issues/214/coveragepy-measures-itself-on-precise
+
+
+.. _changes_353:
+
+Version 3.5.3 --- 2012-09-29
+----------------------------
+
+- Line numbers in the HTML report line up better with the source lines, fixing
+ `issue 197`_, thanks Marius Gedminas.
+
+- When specifying a directory as the source= option, the directory itself no
+ longer needs to have a ``__init__.py`` file, though its sub-directories do,
+ to be considered as source files.
+
+- Files encoded as UTF-8 with a BOM are now properly handled, fixing
+ `issue 179`_. Thanks, Pablo Carballo.
+
+- Fixed more cases of non-Python files being reported as Python source, and
+ then not being able to parse them as Python. Closes `issue 82`_ (again).
+ Thanks, Julian Berman.
+
+- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_.
+
+- Optimized .pyo files may not have been handled correctly, `issue 195`_.
+ Thanks, Marius Gedminas.
+
+- Certain unusually named file paths could have been mangled during reporting,
+ `issue 194`_. Thanks, Marius Gedminas.
+
+- Try to do a better job of the impossible task of detecting when we can't
+ build the C extension, fixing `issue 183`_.
+
+- Testing is now done with `tox`_, thanks, Marc Abramowitz.
+
+.. _issue 147: https://bitbucket.org/ned/coveragepy/issues/147/massive-memory-usage-by-ctracer
+.. _issue 179: https://bitbucket.org/ned/coveragepy/issues/179/htmlreporter-fails-when-source-file-is
+.. _issue 183: https://bitbucket.org/ned/coveragepy/issues/183/install-fails-for-python-23
+.. _issue 194: https://bitbucket.org/ned/coveragepy/issues/194/filelocatorrelative_filename-could-mangle
+.. _issue 195: https://bitbucket.org/ned/coveragepy/issues/195/pyo-file-handling-in-codeunit
+.. _issue 197: https://bitbucket.org/ned/coveragepy/issues/197/line-numbers-in-html-report-do-not-align
+.. _tox: https://tox.readthedocs.io/
+
+
+.. _changes_352:
+
+Version 3.5.2 --- 2012-05-04
+----------------------------
+
+No changes since 3.5.2.b1
+
+
+Version 3.5.2b1 --- 2012-04-29
+------------------------------
+
+- The HTML report has slightly tweaked controls: the buttons at the top of
+ the page are color-coded to the source lines they affect.
+
+- Custom CSS can be applied to the HTML report by specifying a CSS file as
+ the ``extra_css`` configuration value in the ``[html]`` section.
+
+- Source files with custom encodings declared in a comment at the top are now
+ properly handled during reporting on Python 2. Python 3 always handled them
+ properly. This fixes `issue 157`_.
+
+- Backup files left behind by editors are no longer collected by the source=
+ option, fixing `issue 168`_.
+
+- If a file doesn't parse properly as Python, we don't report it as an error
+ if the file name seems like maybe it wasn't meant to be Python. This is a
+ pragmatic fix for `issue 82`_.
+
+- The ``-m`` switch on ``coverage report``, which includes missing line numbers
+ in the summary report, can now be specified as ``show_missing`` in the
+ config file. Closes `issue 173`_.
+
+- When running a module with ``coverage run -m <modulename>``, certain details
+ of the execution environment weren't the same as for
+ ``python -m <modulename>``. This had the unfortunate side-effect of making
+ ``coverage run -m unittest discover`` not work if you had tests in a
+ directory named "test". This fixes `issue 155`_ and `issue 142`_.
+
+- Now the exit status of your product code is properly used as the process
+ status when running ``python -m coverage run ...``. Thanks, JT Olds.
+
+- When installing into pypy, we no longer attempt (and fail) to compile
+ the C tracer function, closing `issue 166`_.
+
+.. _issue 142: https://bitbucket.org/ned/coveragepy/issues/142/executing-python-file-syspath-is-replaced
+.. _issue 155: https://bitbucket.org/ned/coveragepy/issues/155/cant-use-coverage-run-m-unittest-discover
+.. _issue 157: https://bitbucket.org/ned/coveragepy/issues/157/chokes-on-source-files-with-non-utf-8
+.. _issue 166: https://bitbucket.org/ned/coveragepy/issues/166/dont-try-to-compile-c-extension-on-pypy
+.. _issue 168: https://bitbucket.org/ned/coveragepy/issues/168/dont-be-alarmed-by-emacs-droppings
+.. _issue 173: https://bitbucket.org/ned/coveragepy/issues/173/theres-no-way-to-specify-show-missing-in
+
+
+.. _changes_351:
+
+Version 3.5.1 --- 2011-09-23
+----------------------------
+
+- The ``[paths]`` feature unfortunately didn't work in real world situations
+ where you wanted to, you know, report on the combined data. Now all paths
+ stored in the combined file are canonicalized properly.
+
+
+Version 3.5.1b1 --- 2011-08-28
+------------------------------
+
+- When combining data files from parallel runs, you can now instruct
+ coverage.py about which directories are equivalent on different machines. A
+ ``[paths]`` section in the configuration file lists paths that are to be
+ considered equivalent. Finishes `issue 17`_.
+
+- for-else constructs are understood better, and don't cause erroneous partial
+ branch warnings. Fixes `issue 122`_.
+
+- Branch coverage for ``with`` statements is improved, fixing `issue 128`_.
+
+- The number of partial branches reported on the HTML summary page was
+ different than the number reported on the individual file pages. This is
+ now fixed.
+
+- An explicit include directive to measure files in the Python installation
+ wouldn't work because of the standard library exclusion. Now the include
+ directive takes precedence, and the files will be measured. Fixes
+ `issue 138`_.
+
+- The HTML report now handles Unicode characters in Python source files
+ properly. This fixes `issue 124`_ and `issue 144`_. Thanks, Devin
+ Jeanpierre.
+
+- In order to help the core developers measure the test coverage of the
+ standard library, Brandon Rhodes devised an aggressive hack to trick Python
+ into running some coverage.py code before anything else in the process.
+ See the coverage/fullcoverage directory if you are interested.
+
+.. _issue 17: https://bitbucket.org/ned/coveragepy/issues/17/support-combining-coverage-data-from
+.. _issue 122: https://bitbucket.org/ned/coveragepy/issues/122/for-else-always-reports-missing-branch
+.. _issue 124: https://bitbucket.org/ned/coveragepy/issues/124/no-arbitrary-unicode-in-html-reports-in
+.. _issue 128: https://bitbucket.org/ned/coveragepy/issues/128/branch-coverage-of-with-statement-in-27
+.. _issue 138: https://bitbucket.org/ned/coveragepy/issues/138/include-should-take-precedence-over-is
+.. _issue 144: https://bitbucket.org/ned/coveragepy/issues/144/failure-generating-html-output-for
+
+
+.. _changes_35:
+
+Version 3.5 --- 2011-06-29
+--------------------------
+
+- The HTML report hotkeys now behave slightly differently when the current
+ chunk isn't visible at all: a chunk on the screen will be selected,
+ instead of the old behavior of jumping to the literal next chunk.
+ The hotkeys now work in Google Chrome. Thanks, Guido van Rossum.
+
+
+Version 3.5b1 --- 2011-06-05
+----------------------------
+
+- The HTML report now has hotkeys. Try ``n``, ``s``, ``m``, ``x``, ``b``,
+ ``p``, and ``c`` on the overview page to change the column sorting.
+ On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing,
+ excluded, and partial line markings. You can navigate the highlighted
+ sections of code by using the ``j`` and ``k`` keys for next and previous.
+ The ``1`` (one) key jumps to the first highlighted section in the file,
+ and ``0`` (zero) scrolls to the top of the file.
+
+- The ``--omit`` and ``--include`` switches now interpret their values more
+ usefully. If the value starts with a wildcard character, it is used as-is.
+ If it does not, it is interpreted relative to the current directory.
+ Closes `issue 121`_.
+
+- Partial branch warnings can now be pragma'd away. The configuration option
+ ``partial_branches`` is a list of regular expressions. Lines matching any of
+ those expressions will never be marked as a partial branch. In addition,
+ there's a built-in list of regular expressions marking statements which
+ should never be marked as partial. This list includes ``while True:``,
+ ``while 1:``, ``if 1:``, and ``if 0:``.
+
+- The ``coverage()`` constructor accepts single strings for the ``omit=`` and
+ ``include=`` arguments, adapting to a common error in programmatic use.
+
+- Modules can now be run directly using ``coverage run -m modulename``, to
+ mirror Python's ``-m`` flag. Closes `issue 95`_, thanks, Brandon Rhodes.
+
+- ``coverage run`` didn't emulate Python accurately in one small detail: the
+ current directory inserted into ``sys.path`` was relative rather than
+ absolute. This is now fixed.
+
+- HTML reporting is now incremental: a record is kept of the data that
+ produced the HTML reports, and only files whose data has changed will
+ be generated. This should make most HTML reporting faster.
+
+- Pathological code execution could disable the trace function behind our
+ backs, leading to incorrect code measurement. Now if this happens,
+ coverage.py will issue a warning, at least alerting you to the problem.
+ Closes `issue 93`_. Thanks to Marius Gedminas for the idea.
+
+- The C-based trace function now behaves properly when saved and restored
+ with ``sys.gettrace()`` and ``sys.settrace()``. This fixes `issue 125`_
+ and `issue 123`_. Thanks, Devin Jeanpierre.
+
+- Source files are now opened with Python 3.2's ``tokenize.open()`` where
+ possible, to get the best handling of Python source files with encodings.
+ Closes `issue 107`_, thanks, Brett Cannon.
+
+- Syntax errors in supposed Python files can now be ignored during reporting
+ with the ``-i`` switch just like other source errors. Closes `issue 115`_.
+
+- Installation from source now succeeds on machines without a C compiler,
+ closing `issue 80`_.
+
+- Coverage.py can now be run directly from a working tree by specifying
+ the directory name to python: ``python coverage_py_working_dir run ...``.
+ Thanks, Brett Cannon.
+
+- A little bit of Jython support: `coverage run` can now measure Jython
+ execution by adapting when $py.class files are traced. Thanks, Adi Roiban.
+ Jython still doesn't provide the Python libraries needed to make
+ coverage reporting work, unfortunately.
+
+- Internally, files are now closed explicitly, fixing `issue 104`_. Thanks,
+ Brett Cannon.
+
+.. _issue 80: https://bitbucket.org/ned/coveragepy/issues/80/is-there-a-duck-typing-way-to-know-we-cant
+.. _issue 93: https://bitbucket.org/ned/coveragepy/issues/93/copying-a-mock-object-breaks-coverage
+.. _issue 95: https://bitbucket.org/ned/coveragepy/issues/95/run-subcommand-should-take-a-module-name
+.. _issue 104: https://bitbucket.org/ned/coveragepy/issues/104/explicitly-close-files
+.. _issue 107: https://bitbucket.org/ned/coveragepy/issues/107/codeparser-not-opening-source-files-with
+.. _issue 115: https://bitbucket.org/ned/coveragepy/issues/115/fail-gracefully-when-reporting-on-file
+.. _issue 121: https://bitbucket.org/ned/coveragepy/issues/121/filename-patterns-are-applied-stupidly
+.. _issue 123: https://bitbucket.org/ned/coveragepy/issues/123/pyeval_settrace-used-in-way-that-breaks
+.. _issue 125: https://bitbucket.org/ned/coveragepy/issues/125/coverage-removes-decoratortoolss-tracing
+
+
+.. _changes_34:
+
+Version 3.4 --- 2010-09-19
+--------------------------
+
+- The XML report is now sorted by package name, fixing `issue 88`_.
+
+- Programs that exited with ``sys.exit()`` with no argument weren't handled
+ properly, producing a coverage.py stack trace. That is now fixed.
+
+.. _issue 88: https://bitbucket.org/ned/coveragepy/issues/88/xml-report-lists-packages-in-random-order
+
+
+Version 3.4b2 --- 2010-09-06
+----------------------------
+
+- Completely unexecuted files can now be included in coverage results, reported
+ as 0% covered. This only happens if the --source option is specified, since
+ coverage.py needs guidance about where to look for source files.
+
+- The XML report output now properly includes a percentage for branch coverage,
+ fixing `issue 65`_ and `issue 81`_.
+
+- Coverage percentages are now displayed uniformly across reporting methods.
+ Previously, different reports could round percentages differently. Also,
+ percentages are only reported as 0% or 100% if they are truly 0 or 100, and
+ are rounded otherwise. Fixes `issue 41`_ and `issue 70`_.
+
+- The precision of reported coverage percentages can be set with the
+ ``[report] precision`` config file setting. Completes `issue 16`_.
+
+- Threads derived from ``threading.Thread`` with an overridden `run` method
+ would report no coverage for the `run` method. This is now fixed, closing
+ `issue 85`_.
+
+.. _issue 16: https://bitbucket.org/ned/coveragepy/issues/16/allow-configuration-of-accuracy-of-percentage-totals
+.. _issue 41: https://bitbucket.org/ned/coveragepy/issues/41/report-says-100-when-it-isnt-quite-there
+.. _issue 65: https://bitbucket.org/ned/coveragepy/issues/65/branch-option-not-reported-in-cobertura
+.. _issue 70: https://bitbucket.org/ned/coveragepy/issues/70/text-report-and-html-report-disagree-on-coverage
+.. _issue 81: https://bitbucket.org/ned/coveragepy/issues/81/xml-report-does-not-have-condition-coverage-attribute-for-lines-with-a
+.. _issue 85: https://bitbucket.org/ned/coveragepy/issues/85/threadrun-isnt-measured
+
+
+Version 3.4b1 --- 2010-08-21
+----------------------------
+
+- BACKWARD INCOMPATIBILITY: the ``--omit`` and ``--include`` switches now take
+ file patterns rather than file prefixes, closing `issue 34`_ and `issue 36`_.
+
+- BACKWARD INCOMPATIBILITY: the `omit_prefixes` argument is gone throughout
+ coverage.py, replaced with `omit`, a list of file name patterns suitable for
+ `fnmatch`. A parallel argument `include` controls what files are included.
+
+- The run command now has a ``--source`` switch, a list of directories or
+ module names. If provided, coverage.py will only measure execution in those
+ source files.
+
+- Various warnings are printed to stderr for problems encountered during data
+ measurement: if a ``--source`` module has no Python source to measure, or is
+ never encountered at all, or if no data is collected.
+
+- The reporting commands (report, annotate, html, and xml) now have an
+ ``--include`` switch to restrict reporting to modules matching those file
+ patterns, similar to the existing ``--omit`` switch. Thanks, Zooko.
+
+- The run command now supports ``--include`` and ``--omit`` to control what
+ modules it measures. This can speed execution and reduce the amount of data
+ during reporting. Thanks Zooko.
+
+- Since coverage.py 3.1, using the Python trace function has been slower than
+ it needs to be. A cache of tracing decisions was broken, but has now been
+ fixed.
+
+- Python 2.7 and 3.2 have introduced new opcodes that are now supported.
+
+- Python files with no statements, for example, empty ``__init__.py`` files,
+ are now reported as having zero statements instead of one. Fixes `issue 1`_.
+
+- Reports now have a column of missed line counts rather than executed line
+ counts, since developers should focus on reducing the missed lines to zero,
+ rather than increasing the executed lines to varying targets. Once
+ suggested, this seemed blindingly obvious.
+
+- Line numbers in HTML source pages are clickable, linking directly to that
+ line, which is highlighted on arrival. Added a link back to the index page
+ at the bottom of each HTML page.
+
+- Programs that call ``os.fork`` will properly collect data from both the child
+ and parent processes. Use ``coverage run -p`` to get two data files that can
+ be combined with ``coverage combine``. Fixes `issue 56`_.
+
+- Coverage.py is now runnable as a module: ``python -m coverage``. Thanks,
+ Brett Cannon.
+
+- When measuring code running in a virtualenv, most of the system library was
+ being measured when it shouldn't have been. This is now fixed.
+
+- Doctest text files are no longer recorded in the coverage data, since they
+ can't be reported anyway. Fixes `issue 52`_ and `issue 61`_.
+
+- Jinja HTML templates compile into Python code using the HTML file name,
+ which confused coverage.py. Now these files are no longer traced, fixing
+ `issue 82`_.
+
+- Source files can have more than one dot in them (foo.test.py), and will be
+ treated properly while reporting. Fixes `issue 46`_.
+
+- Source files with DOS line endings are now properly tokenized for syntax
+ coloring on non-DOS machines. Fixes `issue 53`_.
+
+- Unusual code structure that confused exits from methods with exits from
+ classes is now properly analyzed. See `issue 62`_.
+
+- Asking for an HTML report with no files now shows a nice error message rather
+ than a cryptic failure ('int' object is unsubscriptable). Fixes `issue 59`_.
+
+.. _issue 1: https://bitbucket.org/ned/coveragepy/issues/1/empty-__init__py-files-are-reported-as-1-executable
+.. _issue 34: https://bitbucket.org/ned/coveragepy/issues/34/enhanced-omit-globbing-handling
+.. _issue 36: https://bitbucket.org/ned/coveragepy/issues/36/provide-regex-style-omit
+.. _issue 46: https://bitbucket.org/ned/coveragepy/issues/46
+.. _issue 53: https://bitbucket.org/ned/coveragepy/issues/53
+.. _issue 52: https://bitbucket.org/ned/coveragepy/issues/52/doctesttestfile-confuses-source-detection
+.. _issue 56: https://bitbucket.org/ned/coveragepy/issues/56
+.. _issue 61: https://bitbucket.org/ned/coveragepy/issues/61/annotate-i-doesnt-work
+.. _issue 62: https://bitbucket.org/ned/coveragepy/issues/62
+.. _issue 59: https://bitbucket.org/ned/coveragepy/issues/59/html-report-fails-with-int-object-is
+.. _issue 82: https://bitbucket.org/ned/coveragepy/issues/82/tokenerror-when-generating-html-report
+
+
+.. _changes_331:
+
+Version 3.3.1 --- 2010-03-06
+----------------------------
+
+- Using `parallel=True` in .coveragerc file prevented reporting, but now does
+ not, fixing `issue 49`_.
+
+- When running your code with "coverage run", if you call `sys.exit()`,
+ coverage.py will exit with that status code, fixing `issue 50`_.
+
+.. _issue 49: https://bitbucket.org/ned/coveragepy/issues/49
+.. _issue 50: https://bitbucket.org/ned/coveragepy/issues/50
+
+
+.. _changes_33:
+
+Version 3.3 --- 2010-02-24
+--------------------------
+
+- Settings are now read from a .coveragerc file. A specific file can be
+ specified on the command line with --rcfile=FILE. The name of the file can
+ be programmatically set with the `config_file` argument to the coverage()
+ constructor, or reading a config file can be disabled with
+ `config_file=False`.
+
+- Fixed a problem with nested loops having their branch possibilities
+ mischaracterized: `issue 39`_.
+
+- Added coverage.process_start to enable coverage measurement when Python
+ starts.
+
+- Parallel data file names now have a random number appended to them in
+ addition to the machine name and process id.
+
+- Parallel data files combined with "coverage combine" are deleted after
+ they're combined, to clean up unneeded files. Fixes `issue 40`_.
+
+- Exceptions thrown from product code run with "coverage run" are now displayed
+ without internal coverage.py frames, so the output is the same as when the
+ code is run without coverage.py.
+
+- The `data_suffix` argument to the coverage constructor is now appended with
+ an added dot rather than simply appended, so that .coveragerc files will not
+ be confused for data files.
+
+- Python source files that don't end with a newline can now be executed, fixing
+ `issue 47`_.
+
+- Added an AUTHORS.txt file.
+
+.. _issue 39: https://bitbucket.org/ned/coveragepy/issues/39
+.. _issue 40: https://bitbucket.org/ned/coveragepy/issues/40
+.. _issue 47: https://bitbucket.org/ned/coveragepy/issues/47
+
+
+.. _changes_32:
+
+Version 3.2 --- 2009-12-05
+--------------------------
+
+- Added a ``--version`` option on the command line.
+
+
+Version 3.2b4 --- 2009-12-01
+----------------------------
+
+- Branch coverage improvements:
+
+ - The XML report now includes branch information.
+
+- Click-to-sort HTML report columns are now persisted in a cookie. Viewing
+ a report will sort it first the way you last had a coverage report sorted.
+ Thanks, `Chris Adams`_.
+
+- On Python 3.x, setuptools has been replaced by `Distribute`_.
+
+.. _Distribute: https://pypi.org/project/distribute/
+
+
+Version 3.2b3 --- 2009-11-23
+----------------------------
+
+- Fixed a memory leak in the C tracer that was introduced in 3.2b1.
+
+- Branch coverage improvements:
+
+ - Branches to excluded code are ignored.
+
+- The table of contents in the HTML report is now sortable: click the headers
+ on any column. Thanks, `Chris Adams`_.
+
+.. _Chris Adams: http://chris.improbable.org
+
+
+Version 3.2b2 --- 2009-11-19
+----------------------------
+
+- Branch coverage improvements:
+
+ - Classes are no longer incorrectly marked as branches: `issue 32`_.
+
+ - "except" clauses with types are no longer incorrectly marked as branches:
+ `issue 35`_.
+
+- Fixed some problems syntax coloring sources with line continuations and
+ source with tabs: `issue 30`_ and `issue 31`_.
+
+- The --omit option now works much better than before, fixing `issue 14`_ and
+ `issue 33`_. Thanks, Danek Duvall.
+
+.. _issue 14: https://bitbucket.org/ned/coveragepy/issues/14
+.. _issue 30: https://bitbucket.org/ned/coveragepy/issues/30
+.. _issue 31: https://bitbucket.org/ned/coveragepy/issues/31
+.. _issue 32: https://bitbucket.org/ned/coveragepy/issues/32
+.. _issue 33: https://bitbucket.org/ned/coveragepy/issues/33
+.. _issue 35: https://bitbucket.org/ned/coveragepy/issues/35
+
+
+Version 3.2b1 --- 2009-11-10
+----------------------------
+
+- Branch coverage!
+
+- XML reporting has file paths that let Cobertura find the source code.
+
+- The tracer code has changed, it's a few percent faster.
+
+- Some exceptions reported by the command line interface have been cleaned up
+ so that tracebacks inside coverage.py aren't shown. Fixes `issue 23`_.
+
+.. _issue 23: https://bitbucket.org/ned/coveragepy/issues/23
+
+
+.. _changes_31:
+
+Version 3.1 --- 2009-10-04
+--------------------------
+
+- Source code can now be read from eggs. Thanks, Ross Lawley. Fixes
+ `issue 25`_.
+
+.. _issue 25: https://bitbucket.org/ned/coveragepy/issues/25
+
+
+Version 3.1b1 --- 2009-09-27
+----------------------------
+
+- Python 3.1 is now supported.
+
+- Coverage.py has a new command line syntax with sub-commands. This expands
+ the possibilities for adding features and options in the future. The old
+ syntax is still supported. Try "coverage help" to see the new commands.
+ Thanks to Ben Finney for early help.
+
+- Added an experimental "coverage xml" command for producing coverage reports
+ in a Cobertura-compatible XML format. Thanks, Bill Hart.
+
+- Added the --timid option to enable a simpler slower trace function that works
+ for DecoratorTools projects, including TurboGears. Fixed `issue 12`_ and
+ `issue 13`_.
+
+- HTML reports show modules from other directories. Fixed `issue 11`_.
+
+- HTML reports now display syntax-colored Python source.
+
+- Programs that change directory will still write .coverage files in the
+ directory where execution started. Fixed `issue 24`_.
+
+- Added a "coverage debug" command for getting diagnostic information about the
+ coverage.py installation.
+
+.. _issue 11: https://bitbucket.org/ned/coveragepy/issues/11
+.. _issue 12: https://bitbucket.org/ned/coveragepy/issues/12
+.. _issue 13: https://bitbucket.org/ned/coveragepy/issues/13
+.. _issue 24: https://bitbucket.org/ned/coveragepy/issues/24
+
+
+.. _changes_301:
+
+Version 3.0.1 --- 2009-07-07
+----------------------------
+
+- Removed the recursion limit in the tracer function. Previously, code that
+ ran more than 500 frames deep would crash. Fixed `issue 9`_.
+
+- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser
+ invocations could be overlooked. Fixed `issue 10`_.
+
+- On Python 2.3, coverage.py could mis-measure code with exceptions being
+ raised. This is now fixed.
+
+- The coverage.py code itself will now not be measured by coverage.py, and no
+ coverage.py modules will be mentioned in the nose --with-cover plug-in.
+ Fixed `issue 8`_.
+
+- When running source files, coverage.py now opens them in universal newline
+ mode just like Python does. This lets it run Windows files on Mac, for
+ example.
+
+.. _issue 9: https://bitbucket.org/ned/coveragepy/issues/9
+.. _issue 10: https://bitbucket.org/ned/coveragepy/issues/10
+.. _issue 8: https://bitbucket.org/ned/coveragepy/issues/8
+
+
+.. _changes_30:
+
+Version 3.0 --- 2009-06-13
+--------------------------
+
+- Fixed the way the Python library was ignored. Too much code was being
+ excluded the old way.
+
+- Tabs are now properly converted in HTML reports. Previously indentation was
+ lost. Fixed `issue 6`_.
+
+- Nested modules now get a proper flat_rootname. Thanks, Christian Heimes.
+
+.. _issue 6: https://bitbucket.org/ned/coveragepy/issues/6
+
+
+Version 3.0b3 --- 2009-05-16
+----------------------------
+
+- Added parameters to coverage.__init__ for options that had been set on the
+ coverage object itself.
+
+- Added clear_exclude() and get_exclude_list() methods for programmatic
+ manipulation of the exclude regexes.
+
+- Added coverage.load() to read previously-saved data from the data file.
+
+- Improved the finding of code files. For example, .pyc files that have been
+ installed after compiling are now located correctly. Thanks, Detlev
+ Offenbach.
+
+- When using the object API (that is, constructing a coverage() object), data
+ is no longer saved automatically on process exit. You can re-enable it with
+ the auto_data=True parameter on the coverage() constructor. The module-level
+ interface still uses automatic saving.
+
+
+Version 3.0b --- 2009-04-30
+---------------------------
+
+HTML reporting, and continued refactoring.
+
+- HTML reports and annotation of source files: use the new -b (browser) switch.
+ Thanks to George Song for code, inspiration and guidance.
+
+- Code in the Python standard library is not measured by default. If you need
+ to measure standard library code, use the -L command-line switch during
+ execution, or the cover_pylib=True argument to the coverage() constructor.
+
+- Source annotation into a directory (-a -d) behaves differently. The
+ annotated files are named with their hierarchy flattened so that same-named
+ files from different directories no longer collide. Also, only files in the
+ current tree are included.
+
+- coverage.annotate_file is no longer available.
+
+- Programs executed with -x now behave more as they should, for example,
+ __file__ has the correct value.
+
+- .coverage data files have a new pickle-based format designed for better
+ extensibility.
+
+- Removed the undocumented cache_file argument to coverage.usecache().
+
+
+Version 3.0b1 --- 2009-03-07
+----------------------------
+
+Major overhaul.
+
+- Coverage.py is now a package rather than a module. Functionality has been
+ split into classes.
+
+- The trace function is implemented in C for speed. Coverage.py runs are now
+ much faster. Thanks to David Christian for productive micro-sprints and
+ other encouragement.
+
+- Executable lines are identified by reading the line number tables in the
+ compiled code, removing a great deal of complicated analysis code.
+
+- Precisely which lines are considered executable has changed in some cases.
+ Therefore, your coverage stats may also change slightly.
+
+- The singleton coverage object is only created if the module-level functions
+ are used. This maintains the old interface while allowing better
+ programmatic use of coverage.py.
+
+- The minimum supported Python version is 2.3.
+
+
+Version 2.85 --- 2008-09-14
+---------------------------
+
+- Add support for finding source files in eggs. Don't check for
+ morf's being instances of ModuleType, instead use duck typing so that
+ pseudo-modules can participate. Thanks, Imri Goldberg.
+
+- Use os.realpath as part of the fixing of file names so that symlinks won't
+ confuse things. Thanks, Patrick Mezard.
+
+
+Version 2.80 --- 2008-05-25
+---------------------------
+
+- Open files in rU mode to avoid line ending craziness. Thanks, Edward Loper.
+
+
+Version 2.78 --- 2007-09-30
+---------------------------
+
+- Don't try to predict whether a file is Python source based on the extension.
+ Extension-less files are often Pythons scripts. Instead, simply parse the
+ file and catch the syntax errors. Hat tip to Ben Finney.
+
+
+Version 2.77 --- 2007-07-29
+---------------------------
+
+- Better packaging.
+
+
+Version 2.76 --- 2007-07-23
+---------------------------
+
+- Now Python 2.5 is *really* fully supported: the body of the new with
+ statement is counted as executable.
+
+
+Version 2.75 --- 2007-07-22
+---------------------------
+
+- Python 2.5 now fully supported. The method of dealing with multi-line
+ statements is now less sensitive to the exact line that Python reports during
+ execution. Pass statements are handled specially so that their disappearance
+ during execution won't throw off the measurement.
+
+
+Version 2.7 --- 2007-07-21
+--------------------------
+
+- "#pragma: nocover" is excluded by default.
+
+- Properly ignore docstrings and other constant expressions that appear in the
+ middle of a function, a problem reported by Tim Leslie.
+
+- coverage.erase() shouldn't clobber the exclude regex. Change how parallel
+ mode is invoked, and fix erase() so that it erases the cache when called
+ programmatically.
+
+- In reports, ignore code executed from strings, since we can't do anything
+ useful with it anyway.
+
+- Better file handling on Linux, thanks Guillaume Chazarain.
+
+- Better shell support on Windows, thanks Noel O'Boyle.
+
+- Python 2.2 support maintained, thanks Catherine Proulx.
+
+- Minor changes to avoid lint warnings.
+
+
+Version 2.6 --- 2006-08-23
+--------------------------
+
+- Applied Joseph Tate's patch for function decorators.
+
+- Applied Sigve Tjora and Mark van der Wal's fixes for argument handling.
+
+- Applied Geoff Bache's parallel mode patch.
+
+- Refactorings to improve testability. Fixes to command-line logic for parallel
+ mode and collect.
+
+
+Version 2.5 --- 2005-12-04
+--------------------------
+
+- Call threading.settrace so that all threads are measured. Thanks Martin
+ Fuzzey.
+
+- Add a file argument to report so that reports can be captured to a different
+ destination.
+
+- Coverage.py can now measure itself.
+
+- Adapted Greg Rogers' patch for using relative file names, and sorting and
+ omitting files to report on.
+
+
+Version 2.2 --- 2004-12-31
+--------------------------
+
+- Allow for keyword arguments in the module global functions. Thanks, Allen.
+
+
+Version 2.1 --- 2004-12-14
+--------------------------
+
+- Return 'analysis' to its original behavior and add 'analysis2'. Add a global
+ for 'annotate', and factor it, adding 'annotate_file'.
+
+
+Version 2.0 --- 2004-12-12
+--------------------------
+
+Significant code changes.
+
+- Finding executable statements has been rewritten so that docstrings and
+ other quirks of Python execution aren't mistakenly identified as missing
+ lines.
+
+- Lines can be excluded from consideration, even entire suites of lines.
+
+- The file system cache of covered lines can be disabled programmatically.
+
+- Modernized the code.
+
+
+Earlier History
+---------------
+
+2001-12-04 GDR Created.
+
+2001-12-06 GDR Added command-line interface and source code annotation.
+
+2001-12-09 GDR Moved design and interface to separate documents.
+
+2001-12-10 GDR Open cache file as binary on Windows. Allow simultaneous -e and
+-x, or -a and -r.
+
+2001-12-12 GDR Added command-line help. Cache analysis so that it only needs to
+be done once when you specify -a and -r.
+
+2001-12-13 GDR Improved speed while recording. Portable between Python 1.5.2
+and 2.1.1.
+
+2002-01-03 GDR Module-level functions work correctly.
+
+2002-01-07 GDR Update sys.path when running a file with the -x option, so that
+it matches the value the program would get if it were run on its own.
diff --git a/third_party/python/coverage/CONTRIBUTORS.txt b/third_party/python/coverage/CONTRIBUTORS.txt
new file mode 100644
index 0000000000..a3cc9be769
--- /dev/null
+++ b/third_party/python/coverage/CONTRIBUTORS.txt
@@ -0,0 +1,136 @@
+Coverage.py was originally written by Gareth Rees, and since 2004 has been
+extended and maintained by Ned Batchelder.
+
+Other contributions, including writing code, updating docs, and submitting
+useful bug reports, have been made by:
+
+Abdeali Kothari
+Adi Roiban
+Agbonze O. Jeremiah
+Albertas Agejevas
+Aleksi Torhamo
+Alex Gaynor
+Alex Groce
+Alex Sandro
+Alexander Todorov
+Alexander Walters
+Andrew Hoos
+Anthony Sottile
+Arcadiy Ivanov
+Aron Griffis
+Artem Dayneko
+Ben Finney
+Bernát Gábor
+Bill Hart
+Brandon Rhodes
+Brett Cannon
+Bruno P. Kinoshita
+Buck Evan
+Calen Pennington
+Carl Gieringer
+Catherine Proulx
+Chris Adams
+Chris Jerdonek
+Chris Rose
+Chris Warrick
+Christian Heimes
+Christine Lytwynec
+Christoph Zwerschke
+Conrad Ho
+Cosimo Lupo
+Dan Hemberger
+Dan Riti
+Dan Wandschneider
+Danek Duvall
+Daniel Hahler
+Danny Allen
+David Christian
+David MacIver
+David Stanek
+David Szotten
+Detlev Offenbach
+Devin Jeanpierre
+Dirk Thomas
+Dmitry Shishov
+Dmitry Trofimov
+Eduardo Schettino
+Eli Skeggs
+Emil Madsen
+Edward Loper
+Federico Bond
+Frazer McLean
+Geoff Bache
+George Paci
+George Song
+George-Cristian Bîrzan
+Greg Rogers
+Guido van Rossum
+Guillaume Chazarain
+Hugo van Kemenade
+Ilia Meerovich
+Imri Goldberg
+Ionel Cristian Mărieș
+JT Olds
+Jessamyn Smith
+Joe Doherty
+Joe Jevnik
+Jon Chappell
+Jon Dufresne
+Joseph Tate
+Josh Williams
+Julian Berman
+Julien Voisin
+Justas Sadzevičius
+Kjell Braden
+Krystian Kichewko
+Kyle Altendorf
+Lars Hupfeldt Nielsen
+Leonardo Pistone
+Lex Berezhny
+Loïc Dachary
+Marc Abramowitz
+Marcus Cobden
+Marius Gedminas
+Mark van der Wal
+Martin Fuzzey
+Matt Bachmann
+Matthew Boehm
+Matthew Desmarais
+Max Linke
+Michał Bultrowicz
+Mickie Betz
+Mike Fiedler
+Nathan Land
+Noel O'Boyle
+Olivier Grisel
+Ori Avtalion
+Pankaj Pandey
+Pablo Carballo
+Patrick Mezard
+Peter Baughman
+Peter Ebden
+Peter Portante
+Reya B
+Rodrigue Cloutier
+Roger Hu
+Ross Lawley
+Roy Williams
+Salvatore Zagaria
+Sandra Martocchia
+Scott Belden
+Sigve Tjora
+Simon Willison
+Stan Hu
+Stefan Behnel
+Stephan Richter
+Stephen Finucane
+Steve Leonard
+Steve Peak
+S. Y. Lee
+Ted Wexler
+Thijs Triemstra
+Titus Brown
+Ville Skyttä
+Yury Selivanov
+Zac Hatfield-Dodds
+Zooko Wilcox-O'Hearn
diff --git a/third_party/python/coverage/LICENSE.txt b/third_party/python/coverage/LICENSE.txt
new file mode 100644
index 0000000000..f433b1a53f
--- /dev/null
+++ b/third_party/python/coverage/LICENSE.txt
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/third_party/python/coverage/MANIFEST.in b/third_party/python/coverage/MANIFEST.in
new file mode 100644
index 0000000000..75257c6068
--- /dev/null
+++ b/third_party/python/coverage/MANIFEST.in
@@ -0,0 +1,49 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# MANIFEST.in file for coverage.py
+
+# This file includes everything needed to recreate the entire project, even
+# though many of these files are not installed by setup.py. Unpacking the
+# .tar.gz source distribution would give you everything needed to continue
+# developing the project. "pip install" will not install many of these files.
+
+include CONTRIBUTORS.txt
+include CHANGES.rst
+include LICENSE.txt
+include MANIFEST.in
+include Makefile
+include NOTICE.txt
+include README.rst
+include __main__.py
+include .travis.yml
+include appveyor.yml
+include howto.txt
+include igor.py
+include metacov.ini
+include pylintrc
+include setup.py
+include tox.ini
+include tox_wheels.ini
+include .editorconfig
+include .readthedocs.yml
+
+recursive-include ci *
+exclude ci/*.token
+
+recursive-include coverage/fullcoverage *.py
+recursive-include coverage/ctracer *.c *.h
+
+recursive-include doc *.py *.pip *.rst *.txt *.png
+recursive-include doc/_static *
+prune doc/_build
+prune doc/_spell
+
+recursive-include requirements *.pip
+
+recursive-include tests *.py *.tok
+recursive-include tests/gold *
+recursive-include tests js/* qunit/*
+prune tests/eggsrc/build
+
+global-exclude *.py[co]
diff --git a/third_party/python/coverage/Makefile b/third_party/python/coverage/Makefile
new file mode 100644
index 0000000000..e1675d9b12
--- /dev/null
+++ b/third_party/python/coverage/Makefile
@@ -0,0 +1,162 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# Makefile for utility work on coverage.py.
+
+help: ## Show this help.
+ @echo "Available targets:"
+ @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf " %-26s%s\n", $$1, $$2}'
+
+clean_platform: ## Remove files that clash across platforms.
+ rm -f *.so */*.so
+ rm -rf __pycache__ */__pycache__ */*/__pycache__ */*/*/__pycache__ */*/*/*/__pycache__ */*/*/*/*/__pycache__
+ rm -f *.pyc */*.pyc */*/*.pyc */*/*/*.pyc */*/*/*/*.pyc */*/*/*/*/*.pyc
+ rm -f *.pyo */*.pyo */*/*.pyo */*/*/*.pyo */*/*/*/*.pyo */*/*/*/*/*.pyo
+
+clean: clean_platform ## Remove artifacts of test execution, installation, etc.
+ -pip uninstall -y coverage
+ rm -f *.pyd */*.pyd
+ rm -rf build coverage.egg-info dist htmlcov
+ rm -f *.bak */*.bak */*/*.bak */*/*/*.bak */*/*/*/*.bak */*/*/*/*/*.bak
+ rm -f *$$py.class */*$$py.class */*/*$$py.class */*/*/*$$py.class */*/*/*/*$$py.class */*/*/*/*/*$$py.class
+ rm -f coverage/*,cover
+ rm -f MANIFEST
+ rm -f .coverage .coverage.* coverage.xml .metacov*
+ rm -f .tox/*/lib/*/site-packages/zzz_metacov.pth
+ rm -f */.coverage */*/.coverage */*/*/.coverage */*/*/*/.coverage */*/*/*/*/.coverage */*/*/*/*/*/.coverage
+ rm -f tests/covmain.zip tests/zipmods.zip
+ rm -rf tests/eggsrc/build tests/eggsrc/dist tests/eggsrc/*.egg-info
+ rm -f setuptools-*.egg distribute-*.egg distribute-*.tar.gz
+ rm -rf doc/_build doc/_spell doc/sample_html_beta
+ rm -rf .cache .pytest_cache .hypothesis
+ rm -rf $$TMPDIR/coverage_test
+ -make -C tests/gold/html clean
+
+sterile: clean ## Remove all non-controlled content, even if expensive.
+ rm -rf .tox
+ -docker image rm -f quay.io/pypa/manylinux1_i686 quay.io/pypa/manylinux1_x86_64
+
+
+CSS = coverage/htmlfiles/style.css
+SCSS = coverage/htmlfiles/style.scss
+
+css: $(CSS) ## Compile .scss into .css.
+$(CSS): $(SCSS)
+ sass --style=compact --sourcemap=none --no-cache $(SCSS) $@
+ cp $@ tests/gold/html/styled
+
+LINTABLE = coverage tests igor.py setup.py __main__.py
+
+lint: ## Run linters and checkers.
+ tox -e lint
+
+todo:
+ -grep -R --include=*.py TODO $(LINTABLE)
+
+pep8:
+ pycodestyle --filename=*.py --repeat $(LINTABLE)
+
+test:
+ tox -e py27,py35 $(ARGS)
+
+PYTEST_SMOKE_ARGS = -n 6 -m "not expensive" --maxfail=3 $(ARGS)
+
+smoke: ## Run tests quickly with the C tracer in the lowest supported Python versions.
+ COVERAGE_NO_PYTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS)
+
+pysmoke: ## Run tests quickly with the Python tracer in the lowest supported Python versions.
+ COVERAGE_NO_CTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS)
+
+DOCKER_RUN = docker run -it --init --rm -v `pwd`:/io
+RUN_MANYLINUX_X86 = $(DOCKER_RUN) quay.io/pypa/manylinux1_x86_64 /io/ci/manylinux.sh
+RUN_MANYLINUX_I686 = $(DOCKER_RUN) quay.io/pypa/manylinux1_i686 /io/ci/manylinux.sh
+
+test_linux: ## Run the tests in Linux under Docker.
+ # The Linux .pyc files clash with the host's because of file path
+ # changes, so clean them before and after running tests.
+ make clean_platform
+ $(RUN_MANYLINUX_X86) test $(ARGS)
+ make clean_platform
+
+meta_linux: ## Run meta-coverage in Linux under Docker.
+ ARGS="meta $(ARGS)" make test_linux
+
+# Coverage measurement of coverage.py itself (meta-coverage). See metacov.ini
+# for details.
+
+metacov: ## Run meta-coverage, measuring ourself.
+ COVERAGE_COVERAGE=yes tox $(ARGS)
+
+metahtml: ## Produce meta-coverage HTML reports.
+ python igor.py combine_html
+
+# Kitting
+
+kit: ## Make the source distribution.
+ python setup.py sdist
+
+wheel: ## Make the wheels for distribution.
+ tox -c tox_wheels.ini $(ARGS)
+
+kit_linux: ## Make the Linux wheels.
+ $(RUN_MANYLINUX_X86) build
+ $(RUN_MANYLINUX_I686) build
+
+kit_upload: ## Upload the built distributions to PyPI.
+ twine upload --verbose dist/*
+
+test_upload: ## Upload the distrubutions to PyPI's testing server.
+ twine upload --verbose --repository testpypi dist/*
+
+kit_local:
+ # pip.conf looks like this:
+ # [global]
+ # find-links = file:///Users/ned/Downloads/local_pypi
+ cp -v dist/* `awk -F "//" '/find-links/ {print $$2}' ~/.pip/pip.conf`
+ # pip caches wheels of things it has installed. Clean them out so we
+ # don't go crazy trying to figure out why our new code isn't installing.
+ find ~/Library/Caches/pip/wheels -name 'coverage-*' -delete
+
+download_appveyor: ## Download the latest Windows artifacts from AppVeyor.
+ python ci/download_appveyor.py nedbat/coveragepy
+
+build_ext:
+ python setup.py build_ext
+
+# Documentation
+
+DOCBIN = .tox/doc/bin
+SPHINXOPTS = -aE
+SPHINXBUILD = $(DOCBIN)/sphinx-build $(SPHINXOPTS)
+SPHINXAUTOBUILD = $(DOCBIN)/sphinx-autobuild -p 9876 --ignore '.git/**' --open-browser
+WEBHOME = ~/web/stellated/
+WEBSAMPLE = $(WEBHOME)/files/sample_coverage_html
+WEBSAMPLEBETA = $(WEBHOME)/files/sample_coverage_html_beta
+
+docreqs:
+ tox -q -e doc --notest
+
+dochtml: docreqs ## Build the docs HTML output.
+ $(DOCBIN)/python doc/check_copied_from.py doc/*.rst
+ $(SPHINXBUILD) -b html doc doc/_build/html
+
+docdev: dochtml ## Build docs, and auto-watch for changes.
+ PATH=$(DOCBIN):$(PATH) $(SPHINXAUTOBUILD) -b html doc doc/_build/html
+
+docspell: docreqs
+ $(SPHINXBUILD) -b spelling doc doc/_spell
+
+publish:
+ rm -f $(WEBSAMPLE)/*.*
+ mkdir -p $(WEBSAMPLE)
+ cp doc/sample_html/*.* $(WEBSAMPLE)
+
+publishbeta:
+ rm -f $(WEBSAMPLEBETA)/*.*
+ mkdir -p $(WEBSAMPLEBETA)
+ cp doc/sample_html_beta/*.* $(WEBSAMPLEBETA)
+
+upload_relnotes: docreqs ## Upload parsed release notes to Tidelift.
+ $(SPHINXBUILD) -b rst doc /tmp/rst_rst
+ pandoc -frst -tmarkdown_strict --atx-headers /tmp/rst_rst/changes.rst > /tmp/rst_rst/changes.md
+ python ci/upload_relnotes.py /tmp/rst_rst/changes.md pypi/coverage
diff --git a/third_party/python/coverage/NOTICE.txt b/third_party/python/coverage/NOTICE.txt
new file mode 100644
index 0000000000..2e7671024e
--- /dev/null
+++ b/third_party/python/coverage/NOTICE.txt
@@ -0,0 +1,14 @@
+Copyright 2001 Gareth Rees. All rights reserved.
+Copyright 2004-2020 Ned Batchelder. All rights reserved.
+
+Except where noted otherwise, this software is licensed under the Apache
+License, Version 2.0 (the "License"); you may not use this work except in
+compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/python/coverage/PKG-INFO b/third_party/python/coverage/PKG-INFO
new file mode 100644
index 0000000000..181e84b15c
--- /dev/null
+++ b/third_party/python/coverage/PKG-INFO
@@ -0,0 +1,187 @@
+Metadata-Version: 2.1
+Name: coverage
+Version: 5.1
+Summary: Code coverage measurement for Python
+Home-page: https://github.com/nedbat/coveragepy
+Author: Ned Batchelder and 131 others
+Author-email: ned@nedbatchelder.com
+License: Apache 2.0
+Project-URL: Documentation, https://coverage.readthedocs.io
+Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi
+Project-URL: Issues, https://github.com/nedbat/coveragepy/issues
+Description: .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+ .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+ ===========
+ Coverage.py
+ ===========
+
+ Code coverage testing for Python.
+
+ | |license| |versions| |status|
+ | |ci-status| |win-ci-status| |docs| |codecov|
+ | |kit| |format| |repos|
+ | |stars| |forks| |contributors|
+ | |tidelift| |twitter-coveragepy| |twitter-nedbat|
+
+ Coverage.py measures code coverage, typically during test execution. It uses
+ the code analysis tools and tracing hooks provided in the Python standard
+ library to determine which lines are executable, and which have been executed.
+
+ Coverage.py runs on many versions of Python:
+
+ * CPython 2.7.
+ * CPython 3.5 through 3.9 alpha 4.
+ * PyPy2 7.3.0 and PyPy3 7.3.0.
+
+ Documentation is on `Read the Docs`_. Code repository and issue tracker are on
+ `GitHub`_.
+
+ .. _Read the Docs: https://coverage.readthedocs.io/
+ .. _GitHub: https://github.com/nedbat/coveragepy
+
+
+ **New in 5.0:** SQLite data storage, JSON report, contexts, relative filenames,
+ dropped support for Python 2.6, 3.3 and 3.4.
+
+
+ For Enterprise
+ --------------
+
+ .. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logo_small.png
+ :width: 75
+ :alt: Tidelift
+ :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+
+ .. list-table::
+ :widths: 10 100
+
+ * - |tideliftlogo|
+ - `Available as part of the Tidelift Subscription. <https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme>`_
+ Coverage and thousands of other packages are working with
+ Tidelift to deliver one enterprise subscription that covers all of the open
+ source you use. If you want the flexibility of open source and the confidence
+ of commercial-grade software, this is for you.
+ `Learn more. <https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme>`_
+
+
+ Getting Started
+ ---------------
+
+ See the `Quick Start section`_ of the docs.
+
+ .. _Quick Start section: https://coverage.readthedocs.io/#quick-start
+
+
+ Change history
+ --------------
+
+ The complete history of changes is on the `change history page`_.
+
+ .. _change history page: https://coverage.readthedocs.io/en/latest/changes.html
+
+
+ Contributing
+ ------------
+
+ See the `Contributing section`_ of the docs.
+
+ .. _Contributing section: https://coverage.readthedocs.io/en/latest/contributing.html
+
+
+ Security
+ --------
+
+ To report a security vulnerability, please use the `Tidelift security
+ contact`_. Tidelift will coordinate the fix and disclosure.
+
+ .. _Tidelift security contact: https://tidelift.com/security
+
+
+ License
+ -------
+
+ Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_.
+
+ .. _Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
+ .. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+
+ .. |ci-status| image:: https://travis-ci.com/nedbat/coveragepy.svg?branch=master
+ :target: https://travis-ci.com/nedbat/coveragepy
+ :alt: Build status
+ .. |win-ci-status| image:: https://ci.appveyor.com/api/projects/status/kmeqpdje7h9r6vsf/branch/master?svg=true
+ :target: https://ci.appveyor.com/project/nedbat/coveragepy
+ :alt: Windows build status
+ .. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
+ :target: https://coverage.readthedocs.io/
+ :alt: Documentation
+ .. |reqs| image:: https://requires.io/github/nedbat/coveragepy/requirements.svg?branch=master
+ :target: https://requires.io/github/nedbat/coveragepy/requirements/?branch=master
+ :alt: Requirements status
+ .. |kit| image:: https://badge.fury.io/py/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: PyPI status
+ .. |format| image:: https://img.shields.io/pypi/format/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Kit format
+ .. |downloads| image:: https://img.shields.io/pypi/dw/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Weekly PyPI downloads
+ .. |versions| image:: https://img.shields.io/pypi/pyversions/coverage.svg?logo=python&logoColor=FBE072
+ :target: https://pypi.org/project/coverage/
+ :alt: Python versions supported
+ .. |status| image:: https://img.shields.io/pypi/status/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Package stability
+ .. |license| image:: https://img.shields.io/pypi/l/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: License
+ .. |codecov| image:: https://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2
+ :target: https://codecov.io/github/nedbat/coveragepy?branch=master
+ :alt: Coverage!
+ .. |repos| image:: https://repology.org/badge/tiny-repos/python:coverage.svg
+ :target: https://repology.org/metapackage/python:coverage/versions
+ :alt: Packaging status
+ .. |tidelift| image:: https://tidelift.com/badges/package/pypi/coverage
+ :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+ :alt: Tidelift
+ .. |stars| image:: https://img.shields.io/github/stars/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/stargazers
+ :alt: Github stars
+ .. |forks| image:: https://img.shields.io/github/forks/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/network/members
+ :alt: Github forks
+ .. |contributors| image:: https://img.shields.io/github/contributors/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/graphs/contributors
+ :alt: Contributors
+ .. |twitter-coveragepy| image:: https://img.shields.io/twitter/follow/coveragepy.svg?label=coveragepy&style=flat&logo=twitter&logoColor=4FADFF
+ :target: https://twitter.com/coveragepy
+ :alt: coverage.py on Twitter
+ .. |twitter-nedbat| image:: https://img.shields.io/twitter/follow/nedbat.svg?label=nedbat&style=flat&logo=twitter&logoColor=4FADFF
+ :target: https://twitter.com/nedbat
+ :alt: nedbat on Twitter
+
+Keywords: code coverage testing
+Platform: UNKNOWN
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Quality Assurance
+Classifier: Topic :: Software Development :: Testing
+Classifier: Development Status :: 5 - Production/Stable
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4
+Description-Content-Type: text/x-rst
+Provides-Extra: toml
diff --git a/third_party/python/coverage/README.rst b/third_party/python/coverage/README.rst
new file mode 100644
index 0000000000..4534bc92a6
--- /dev/null
+++ b/third_party/python/coverage/README.rst
@@ -0,0 +1,152 @@
+.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+===========
+Coverage.py
+===========
+
+Code coverage testing for Python.
+
+| |license| |versions| |status|
+| |ci-status| |win-ci-status| |docs| |codecov|
+| |kit| |format| |repos|
+| |stars| |forks| |contributors|
+| |tidelift| |twitter-coveragepy| |twitter-nedbat|
+
+Coverage.py measures code coverage, typically during test execution. It uses
+the code analysis tools and tracing hooks provided in the Python standard
+library to determine which lines are executable, and which have been executed.
+
+Coverage.py runs on many versions of Python:
+
+* CPython 2.7.
+* CPython 3.5 through 3.9 alpha 4.
+* PyPy2 7.3.0 and PyPy3 7.3.0.
+
+Documentation is on `Read the Docs`_. Code repository and issue tracker are on
+`GitHub`_.
+
+.. _Read the Docs: https://coverage.readthedocs.io/
+.. _GitHub: https://github.com/nedbat/coveragepy
+
+
+**New in 5.0:** SQLite data storage, JSON report, contexts, relative filenames,
+dropped support for Python 2.6, 3.3 and 3.4.
+
+
+For Enterprise
+--------------
+
+.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logo_small.png
+ :width: 75
+ :alt: Tidelift
+ :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+
+.. list-table::
+ :widths: 10 100
+
+ * - |tideliftlogo|
+ - `Available as part of the Tidelift Subscription. <https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme>`_
+ Coverage and thousands of other packages are working with
+ Tidelift to deliver one enterprise subscription that covers all of the open
+ source you use. If you want the flexibility of open source and the confidence
+ of commercial-grade software, this is for you.
+ `Learn more. <https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme>`_
+
+
+Getting Started
+---------------
+
+See the `Quick Start section`_ of the docs.
+
+.. _Quick Start section: https://coverage.readthedocs.io/#quick-start
+
+
+Change history
+--------------
+
+The complete history of changes is on the `change history page`_.
+
+.. _change history page: https://coverage.readthedocs.io/en/latest/changes.html
+
+
+Contributing
+------------
+
+See the `Contributing section`_ of the docs.
+
+.. _Contributing section: https://coverage.readthedocs.io/en/latest/contributing.html
+
+
+Security
+--------
+
+To report a security vulnerability, please use the `Tidelift security
+contact`_. Tidelift will coordinate the fix and disclosure.
+
+.. _Tidelift security contact: https://tidelift.com/security
+
+
+License
+-------
+
+Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_.
+
+.. _Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
+.. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+
+.. |ci-status| image:: https://travis-ci.com/nedbat/coveragepy.svg?branch=master
+ :target: https://travis-ci.com/nedbat/coveragepy
+ :alt: Build status
+.. |win-ci-status| image:: https://ci.appveyor.com/api/projects/status/kmeqpdje7h9r6vsf/branch/master?svg=true
+ :target: https://ci.appveyor.com/project/nedbat/coveragepy
+ :alt: Windows build status
+.. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
+ :target: https://coverage.readthedocs.io/
+ :alt: Documentation
+.. |reqs| image:: https://requires.io/github/nedbat/coveragepy/requirements.svg?branch=master
+ :target: https://requires.io/github/nedbat/coveragepy/requirements/?branch=master
+ :alt: Requirements status
+.. |kit| image:: https://badge.fury.io/py/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: PyPI status
+.. |format| image:: https://img.shields.io/pypi/format/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Kit format
+.. |downloads| image:: https://img.shields.io/pypi/dw/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Weekly PyPI downloads
+.. |versions| image:: https://img.shields.io/pypi/pyversions/coverage.svg?logo=python&logoColor=FBE072
+ :target: https://pypi.org/project/coverage/
+ :alt: Python versions supported
+.. |status| image:: https://img.shields.io/pypi/status/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: Package stability
+.. |license| image:: https://img.shields.io/pypi/l/coverage.svg
+ :target: https://pypi.org/project/coverage/
+ :alt: License
+.. |codecov| image:: https://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2
+ :target: https://codecov.io/github/nedbat/coveragepy?branch=master
+ :alt: Coverage!
+.. |repos| image:: https://repology.org/badge/tiny-repos/python:coverage.svg
+ :target: https://repology.org/metapackage/python:coverage/versions
+ :alt: Packaging status
+.. |tidelift| image:: https://tidelift.com/badges/package/pypi/coverage
+ :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+ :alt: Tidelift
+.. |stars| image:: https://img.shields.io/github/stars/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/stargazers
+ :alt: Github stars
+.. |forks| image:: https://img.shields.io/github/forks/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/network/members
+ :alt: Github forks
+.. |contributors| image:: https://img.shields.io/github/contributors/nedbat/coveragepy.svg?logo=github
+ :target: https://github.com/nedbat/coveragepy/graphs/contributors
+ :alt: Contributors
+.. |twitter-coveragepy| image:: https://img.shields.io/twitter/follow/coveragepy.svg?label=coveragepy&style=flat&logo=twitter&logoColor=4FADFF
+ :target: https://twitter.com/coveragepy
+ :alt: coverage.py on Twitter
+.. |twitter-nedbat| image:: https://img.shields.io/twitter/follow/nedbat.svg?label=nedbat&style=flat&logo=twitter&logoColor=4FADFF
+ :target: https://twitter.com/nedbat
+ :alt: nedbat on Twitter
diff --git a/third_party/python/coverage/__main__.py b/third_party/python/coverage/__main__.py
new file mode 100644
index 0000000000..28ad7d2da4
--- /dev/null
+++ b/third_party/python/coverage/__main__.py
@@ -0,0 +1,12 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Be able to execute coverage.py by pointing Python at a working tree."""
+
+import runpy
+import os
+
+PKG = 'coverage'
+
+run_globals = runpy.run_module(PKG, run_name='__main__', alter_sys=True)
+executed = os.path.splitext(os.path.basename(run_globals['__file__']))[0]
diff --git a/third_party/python/coverage/appveyor.yml b/third_party/python/coverage/appveyor.yml
new file mode 100644
index 0000000000..76e21dad55
--- /dev/null
+++ b/third_party/python/coverage/appveyor.yml
@@ -0,0 +1,169 @@
+# Appveyor, continuous integration for Windows
+# https://ci.appveyor.com/project/nedbat/coveragepy
+
+version: '{branch}-{build}'
+
+shallow_clone: true
+
+cache:
+ - '%LOCALAPPDATA%\pip\Cache'
+
+environment:
+
+ CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\run_with_env.cmd"
+
+ PYTEST_ADDOPTS: "-n auto"
+
+ # Note: There is logic to install Python version $PYTHON_VERSION if the
+ # $PYTHON directory doesn't exist. $PYTHON_VERSION is visible in the job
+ # descriptions, but can be wrong in the minor version, since we use the
+ # version pre-installed on AppVeyor.
+ #
+ matrix:
+ - JOB: "2.7 64-bit"
+ TOXENV: "py27"
+ PYTHON: "C:\\Python27-x64"
+ PYTHON_VERSION: "2.7.17"
+ PYTHON_ARCH: "64"
+
+ - JOB: "3.5 64-bit"
+ TOXENV: "py35"
+ PYTHON: "C:\\Python35-x64"
+ PYTHON_VERSION: "3.5.9"
+ PYTHON_ARCH: "64"
+
+ - JOB: "3.6 64-bit"
+ TOXENV: "py36"
+ PYTHON: "C:\\Python36-x64"
+ PYTHON_VERSION: "3.6.9"
+ PYTHON_ARCH: "64"
+
+ - JOB: "3.7 64-bit"
+ TOXENV: "py37"
+ PYTHON: "C:\\Python37-x64"
+ PYTHON_VERSION: "3.7.5"
+ PYTHON_ARCH: "64"
+
+ - JOB: "3.8 64-bit"
+ TOXENV: "py38"
+ PYTHON: "C:\\Python38-x64"
+ PYTHON_VERSION: "3.8.0"
+ PYTHON_ARCH: "64"
+
+ - JOB: "3.9 64-bit"
+ TOXENV: "py39"
+ PYTHON: "C:\\Python39-x64"
+ PYTHON_VERSION: "3.9.0a3"
+ PYTHON_ARCH: "64"
+
+ # 32-bit jobs don't run the tests under the Python tracer, since that should
+ # be exactly the same as 64-bit.
+ - JOB: "2.7 32-bit"
+ TOXENV: "py27"
+ PYTHON: "C:\\Python27"
+ PYTHON_VERSION: "2.7.17"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ - JOB: "3.5 32-bit"
+ TOXENV: "py35"
+ PYTHON: "C:\\Python35"
+ PYTHON_VERSION: "3.5.9"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ - JOB: "3.6 32-bit"
+ TOXENV: "py36"
+ PYTHON: "C:\\Python36"
+ PYTHON_VERSION: "3.6.9"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ - JOB: "3.7 32-bit"
+ TOXENV: "py37"
+ PYTHON: "C:\\Python37"
+ PYTHON_VERSION: "3.7.5"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ - JOB: "3.8 32-bit"
+ TOXENV: "py38"
+ PYTHON: "C:\\Python38"
+ PYTHON_VERSION: "3.8.0"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ - JOB: "3.9 32-bit"
+ TOXENV: "py39"
+ PYTHON: "C:\\Python39"
+ PYTHON_VERSION: "3.9.0a3"
+ PYTHON_ARCH: "32"
+ COVERAGE_NO_PYTRACER: "1"
+
+ # Meta coverage
+ - JOB: "Meta 2.7"
+ TOXENV: "py27"
+ PYTHON: "C:\\Python27"
+ PYTHON_VERSION: "2.7.17"
+ PYTHON_ARCH: "32"
+ COVERAGE_COVERAGE: "yes"
+
+ - JOB: "Meta 3.6"
+ TOXENV: "py36"
+ PYTHON: "C:\\Python36"
+ PYTHON_VERSION: "3.6.9"
+ PYTHON_ARCH: "32"
+ COVERAGE_COVERAGE: "yes"
+
+init:
+ - "ECHO %TOXENV%"
+
+install:
+ # Install Python (from the official .msi of http://python.org) and pip when
+ # not already installed.
+ - ps: if (-not(Test-Path($env:PYTHON))) { & ci\install.ps1 }
+
+ # Prepend newly installed Python to the PATH of this build (this cannot be
+ # done from inside the powershell script as it would require to restart
+ # the parent CMD process).
+ - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
+
+ # Check that we have the expected version and architecture for Python
+ - "python -c \"import struct, sys; print('{}\\n{}-bit'.format(sys.version, struct.calcsize('P') * 8))\""
+
+ # Upgrade to the latest version of pip to avoid it displaying warnings
+ # about it being out of date.
+ - "python -m pip install --disable-pip-version-check --upgrade pip"
+ # And upgrade virtualenv to get the latest pip inside .tox virtualenvs.
+ - "python -m pip install --disable-pip-version-check --upgrade virtualenv"
+
+ # Install requirements.
+ - "%CMD_IN_ENV% pip install -r requirements/ci.pip"
+
+ # Make a pythonX.Y.bat file in the current directory so that tox will find it
+ # and pythonX.Y will mean what we want it to.
+ - "python -c \"import os; open('python{}.{}.bat'.format(*os.environ['TOXENV'][2:]), 'w').write('@{}\\\\python \\x25*\\n'.format(os.environ['PYTHON']))\""
+
+build_script:
+ # If not a metacov job, then build wheel installers.
+ - if NOT "%COVERAGE_COVERAGE%" == "yes" %CMD_IN_ENV% %PYTHON%\python setup.py bdist_wheel
+
+ # Push everything in dist\ as an artifact.
+ - ps: if ( Test-Path 'dist' -PathType Container ) { Get-ChildItem dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName ('dist\' + $_.Name) } }
+
+test_script:
+ - "%CMD_IN_ENV% %PYTHON%\\Scripts\\tox"
+
+after_test:
+ - if "%COVERAGE_COVERAGE%" == "yes" 7z a metacov-win-%TOXENV%.zip %APPVEYOR_BUILD_FOLDER%\.metacov*
+ - if "%COVERAGE_COVERAGE%" == "yes" %CMD_IN_ENV% %PYTHON%\python igor.py combine_html
+ - if "%COVERAGE_COVERAGE%" == "yes" %CMD_IN_ENV% pip install codecov
+ - if "%COVERAGE_COVERAGE%" == "yes" %CMD_IN_ENV% codecov -X gcov --file coverage.xml
+
+# Uncomment this to enable RDP access to the build when done.
+# https://www.appveyor.com/docs/how-to/rdp-to-build-worker/
+# on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
+artifacts:
+ - path: "metacov-*.zip"
diff --git a/third_party/python/coverage/ci/README.txt b/third_party/python/coverage/ci/README.txt
new file mode 100644
index 0000000000..a34d036bb0
--- /dev/null
+++ b/third_party/python/coverage/ci/README.txt
@@ -0,0 +1 @@
+Files to support continuous integration systems.
diff --git a/third_party/python/coverage/ci/download_appveyor.py b/third_party/python/coverage/ci/download_appveyor.py
new file mode 100644
index 0000000000..a3d814962d
--- /dev/null
+++ b/third_party/python/coverage/ci/download_appveyor.py
@@ -0,0 +1,95 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Use the Appveyor API to download Windows artifacts."""
+
+import os
+import os.path
+import sys
+import zipfile
+
+import requests
+
+
+def make_auth_headers():
+ """Make the authentication headers needed to use the Appveyor API."""
+ with open("ci/appveyor.token") as f:
+ token = f.read().strip()
+
+ headers = {
+ 'Authorization': 'Bearer {}'.format(token),
+ }
+ return headers
+
+
+def make_url(url, **kwargs):
+ """Build an Appveyor API url."""
+ return "https://ci.appveyor.com/api" + url.format(**kwargs)
+
+
+def get_project_build(account_project):
+ """Get the details of the latest Appveyor build."""
+ url = make_url("/projects/{account_project}", account_project=account_project)
+ response = requests.get(url, headers=make_auth_headers())
+ return response.json()
+
+
+def download_latest_artifacts(account_project):
+ """Download all the artifacts from the latest build."""
+ build = get_project_build(account_project)
+ jobs = build['build']['jobs']
+ print("Build {0[build][version]}, {1} jobs: {0[build][message]}".format(build, len(jobs)))
+ for job in jobs:
+ name = job['name'].partition(':')[2].split(',')[0].strip()
+ print(" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, job))
+
+ url = make_url("/buildjobs/{jobid}/artifacts", jobid=job['jobId'])
+ response = requests.get(url, headers=make_auth_headers())
+ artifacts = response.json()
+
+ for artifact in artifacts:
+ is_zip = artifact['type'] == "Zip"
+ filename = artifact['fileName']
+ print(" {}, {} bytes".format(filename, artifact['size']))
+
+ url = make_url(
+ "/buildjobs/{jobid}/artifacts/{filename}",
+ jobid=job['jobId'],
+ filename=filename
+ )
+ download_url(url, filename, make_auth_headers())
+
+ if is_zip:
+ unpack_zipfile(filename)
+ os.remove(filename)
+
+
+def ensure_dirs(filename):
+ """Make sure the directories exist for `filename`."""
+ dirname, _ = os.path.split(filename)
+ if dirname and not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+
+def download_url(url, filename, headers):
+ """Download a file from `url` to `filename`."""
+ ensure_dirs(filename)
+ response = requests.get(url, headers=headers, stream=True)
+ if response.status_code == 200:
+ with open(filename, 'wb') as f:
+ for chunk in response.iter_content(16*1024):
+ f.write(chunk)
+
+
+def unpack_zipfile(filename):
+ """Unpack a zipfile, using the names in the zip."""
+ with open(filename, 'rb') as fzip:
+ z = zipfile.ZipFile(fzip)
+ for name in z.namelist():
+ print(" extracting {}".format(name))
+ ensure_dirs(name)
+ z.extract(name)
+
+
+if __name__ == "__main__":
+ download_latest_artifacts(sys.argv[1])
diff --git a/third_party/python/coverage/ci/install.ps1 b/third_party/python/coverage/ci/install.ps1
new file mode 100644
index 0000000000..fd5ab22021
--- /dev/null
+++ b/third_party/python/coverage/ci/install.ps1
@@ -0,0 +1,203 @@
+# From: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/install.ps1
+#
+#
+# Sample script to install Python and pip under Windows
+# Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer
+# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+
+$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
+$BASE_URL = "https://www.python.org/ftp/python/"
+$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
+$GET_PIP_PATH = "C:\get-pip.py"
+
+$PYTHON_PRERELEASE_REGEX = @"
+(?x)
+(?<major>\d+)
+\.
+(?<minor>\d+)
+\.
+(?<micro>\d+)
+(?<prerelease>[a-z]{1,2}\d+)
+"@
+
+
+function Download ($filename, $url) {
+ $webclient = New-Object System.Net.WebClient
+
+ $basedir = $pwd.Path + "\"
+ $filepath = $basedir + $filename
+ if (Test-Path $filename) {
+ Write-Host "Reusing" $filepath
+ return $filepath
+ }
+
+ # Download and retry up to 3 times in case of network transient errors.
+ Write-Host "Downloading" $filename "from" $url
+ $retry_attempts = 2
+ for ($i = 0; $i -lt $retry_attempts; $i++) {
+ try {
+ $webclient.DownloadFile($url, $filepath)
+ break
+ }
+ Catch [Exception]{
+ Start-Sleep 1
+ }
+ }
+ if (Test-Path $filepath) {
+ Write-Host "File saved at" $filepath
+ } else {
+ # Retry once to get the error message if any at the last try
+ $webclient.DownloadFile($url, $filepath)
+ }
+ return $filepath
+}
+
+
+function ParsePythonVersion ($python_version) {
+ if ($python_version -match $PYTHON_PRERELEASE_REGEX) {
+ return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro,
+ $matches.prerelease)
+ }
+ $version_obj = [version]$python_version
+ return ($version_obj.major, $version_obj.minor, $version_obj.build, "")
+}
+
+
+function DownloadPython ($python_version, $platform_suffix) {
+ $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version
+
+ $dir = "$major.$minor.$micro"
+ $ext = "exe"
+ if ($platform_suffix) {
+ $platform_suffix = "-$platform_suffix"
+ }
+
+ $filename = "python-$python_version$platform_suffix.$ext"
+ $url = "$BASE_URL$dir/$filename"
+ $filepath = Download $filename $url
+ return $filepath
+}
+
+
+function InstallPython ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = ""
+ } else {
+ $platform_suffix = "amd64"
+ }
+ $installer_path = DownloadPython $python_version $platform_suffix
+ $installer_ext = [System.IO.Path]::GetExtension($installer_path)
+ Write-Host "Installing $installer_path to $python_home"
+ $install_log = $python_home + ".log"
+ if ($installer_ext -eq '.msi') {
+ InstallPythonMSI $installer_path $python_home $install_log
+ } else {
+ InstallPythonEXE $installer_path $python_home $install_log
+ }
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+
+function InstallPythonEXE ($exepath, $python_home, $install_log) {
+ $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home"
+ RunCommand $exepath $install_args
+}
+
+
+function InstallPythonMSI ($msipath, $python_home, $install_log) {
+ $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
+ $uninstall_args = "/qn /x $msipath"
+ RunCommand "msiexec.exe" $install_args
+ if (-not(Test-Path $python_home)) {
+ Write-Host "Python seems to be installed else-where, reinstalling."
+ RunCommand "msiexec.exe" $uninstall_args
+ RunCommand "msiexec.exe" $install_args
+ }
+}
+
+function RunCommand ($command, $command_args) {
+ Write-Host $command $command_args
+ Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
+}
+
+
+function InstallPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $python_path = $python_home + "\python.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $webclient = New-Object System.Net.WebClient
+ $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
+ Write-Host "Executing:" $python_path $GET_PIP_PATH
+ & $python_path $GET_PIP_PATH
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+
+function DownloadMiniconda ($python_version, $platform_suffix) {
+ $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
+ $url = $MINICONDA_URL + $filename
+ $filepath = Download $filename $url
+ return $filepath
+}
+
+
+function InstallMiniconda ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = "x86"
+ } else {
+ $platform_suffix = "x86_64"
+ }
+ $filepath = DownloadMiniconda $python_version $platform_suffix
+ Write-Host "Installing" $filepath "to" $python_home
+ $install_log = $python_home + ".log"
+ $args = "/S /D=$python_home"
+ Write-Host $filepath $args
+ Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+
+function InstallMinicondaPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $conda_path = $python_home + "\Scripts\conda.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $args = "install --yes pip"
+ Write-Host $conda_path $args
+ Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+function main () {
+ InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
+ InstallPip $env:PYTHON
+}
+
+main
diff --git a/third_party/python/coverage/ci/manylinux.sh b/third_party/python/coverage/ci/manylinux.sh
new file mode 100755
index 0000000000..1fafec9ded
--- /dev/null
+++ b/third_party/python/coverage/ci/manylinux.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# From: https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.sh
+# which is in the public domain.
+#
+# This is run inside a CentOS 5 virtual machine to build manylinux wheels:
+#
+# $ docker run -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/ci/build_manylinux.sh
+#
+
+set -e -x
+
+action=$1
+shift
+
+if [[ $action == "build" ]]; then
+ # Compile wheels
+ cd /io
+ for PYBIN in /opt/python/*/bin; do
+ if [[ $PYBIN == *cp34* ]]; then
+ # manylinux docker images have Python 3.4, but we don't use it.
+ continue
+ fi
+ "$PYBIN/pip" install -r requirements/wheel.pip
+ "$PYBIN/python" setup.py clean -a
+ "$PYBIN/python" setup.py bdist_wheel -d ~/wheelhouse/
+ done
+ cd ~
+
+ # Bundle external shared libraries into the wheels
+ for whl in wheelhouse/*.whl; do
+ auditwheel repair "$whl" -w /io/dist/
+ done
+
+elif [[ $action == "test" ]]; then
+ # Create "pythonX.Y" links
+ for PYBIN in /opt/python/*/bin/; do
+ if [[ $PYBIN == *cp34* ]]; then
+ # manylinux docker images have Python 3.4, but we don't use it.
+ continue
+ fi
+ PYNAME=$("$PYBIN/python" -c "import sys; print('python{0[0]}.{0[1]}'.format(sys.version_info))")
+ ln -sf "$PYBIN/$PYNAME" /usr/local/bin/$PYNAME
+ done
+
+ # Install packages and test
+ TOXBIN=/opt/python/cp36-cp36m/bin
+ "$TOXBIN/pip" install -r /io/requirements/tox.pip
+
+ cd /io
+ export PYTHONPYCACHEPREFIX=/opt/pyc
+ if [[ $1 == "meta" ]]; then
+ shift
+ export COVERAGE_COVERAGE=yes
+ fi
+ TOXWORKDIR=.tox/linux "$TOXBIN/tox" "$@" || true
+ cd ~
+
+else
+ echo "Need an action to perform!"
+fi
diff --git a/third_party/python/coverage/ci/run_with_env.cmd b/third_party/python/coverage/ci/run_with_env.cmd
new file mode 100644
index 0000000000..66b9252efc
--- /dev/null
+++ b/third_party/python/coverage/ci/run_with_env.cmd
@@ -0,0 +1,91 @@
+:: From: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/run_with_env.cmd
+::
+::
+:: To build extensions for 64 bit Python 3, we need to configure environment
+:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
+::
+:: To build extensions for 64 bit Python 2, we need to configure environment
+:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
+::
+:: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific
+:: environment configurations.
+::
+:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
+:: cmd interpreter, at least for (SDK v7.0)
+::
+:: More details at:
+:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
+:: http://stackoverflow.com/a/13751649/163740
+::
+:: Author: Olivier Grisel
+:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+::
+:: Notes about batch files for Python people:
+::
+:: Quotes in values are literally part of the values:
+:: SET FOO="bar"
+:: FOO is now five characters long: " b a r "
+:: If you don't want quotes, don't include them on the right-hand side.
+::
+:: The CALL lines at the end of this file look redundant, but if you move them
+:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
+:: case, I don't know why.
+@ECHO OFF
+
+SET COMMAND_TO_RUN=%*
+SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
+SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf
+
+:: Extract the major and minor versions, and allow for the minor version to be
+:: more than 9. This requires the version number to have two dots in it.
+SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1%
+IF "%PYTHON_VERSION:~3,1%" == "." (
+ SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1%
+) ELSE (
+ SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2%
+)
+
+:: Based on the Python version, determine what SDK version to use, and whether
+:: to set the SDK for 64-bit.
+IF %MAJOR_PYTHON_VERSION% == 2 (
+ SET WINDOWS_SDK_VERSION="v7.0"
+ SET SET_SDK_64=Y
+) ELSE (
+ IF %MAJOR_PYTHON_VERSION% == 3 (
+ SET WINDOWS_SDK_VERSION="v7.1"
+ IF %MINOR_PYTHON_VERSION% LEQ 4 (
+ SET SET_SDK_64=Y
+ ) ELSE (
+ SET SET_SDK_64=N
+ IF EXIST "%WIN_WDK%" (
+ :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
+ REN "%WIN_WDK%" 0wdf
+ )
+ )
+ ) ELSE (
+ ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
+ EXIT 1
+ )
+)
+
+IF %PYTHON_ARCH% == 64 (
+ IF %SET_SDK_64% == Y (
+ ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
+ SET DISTUTILS_USE_SDK=1
+ SET MSSdk=1
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+ ) ELSE (
+ ECHO Using default MSVC build environment for 64 bit architecture
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+ )
+) ELSE (
+ ECHO Using default MSVC build environment for 32 bit architecture
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+)
diff --git a/third_party/python/coverage/ci/upload_relnotes.py b/third_party/python/coverage/ci/upload_relnotes.py
new file mode 100644
index 0000000000..630f4d0a3f
--- /dev/null
+++ b/third_party/python/coverage/ci/upload_relnotes.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+"""
+Upload CHANGES.md to Tidelift as Markdown chunks
+
+Put your Tidelift API token in a file called tidelift.token alongside this
+program, for example:
+
+ user/n3IwOpxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc2ZwE4
+
+Run with two arguments: the .md file to parse, and the Tidelift package name:
+
+ python upload_relnotes.py CHANGES.md pypi/coverage
+
+Every section that has something that looks like a version number in it will
+be uploaded as the release notes for that version.
+
+"""
+
+import os.path
+import re
+import sys
+
+import requests
+
+class TextChunkBuffer:
+ """Hold onto text chunks until needed."""
+ def __init__(self):
+ self.buffer = []
+
+ def append(self, text):
+ """Add `text` to the buffer."""
+ self.buffer.append(text)
+
+ def clear(self):
+ """Clear the buffer."""
+ self.buffer = []
+
+ def flush(self):
+ """Produce a ("text", text) tuple if there's anything here."""
+ buffered = "".join(self.buffer).strip()
+ if buffered:
+ yield ("text", buffered)
+ self.clear()
+
+
+def parse_md(lines):
+ """Parse markdown lines, producing (type, text) chunks."""
+ buffer = TextChunkBuffer()
+
+ for line in lines:
+ header_match = re.search(r"^(#+) (.+)$", line)
+ is_header = bool(header_match)
+ if is_header:
+ yield from buffer.flush()
+ hashes, text = header_match.groups()
+ yield (f"h{len(hashes)}", text)
+ else:
+ buffer.append(line)
+
+ yield from buffer.flush()
+
+
+def sections(parsed_data):
+ """Convert a stream of parsed tokens into sections with text and notes.
+
+ Yields a stream of:
+ ('h-level', 'header text', 'text')
+
+ """
+ header = None
+ text = []
+ for ttype, ttext in parsed_data:
+ if ttype.startswith('h'):
+ if header:
+ yield (*header, "\n".join(text))
+ text = []
+ header = (ttype, ttext)
+ elif ttype == "text":
+ text.append(ttext)
+ else:
+ raise Exception(f"Don't know ttype {ttype!r}")
+ yield (*header, "\n".join(text))
+
+
+def relnotes(mdlines):
+ r"""Yield (version, text) pairs from markdown lines.
+
+ Each tuple is a separate version mentioned in the release notes.
+
+ A version is any section with \d\.\d in the header text.
+
+ """
+ for _, htext, text in sections(parse_md(mdlines)):
+ m_version = re.search(r"\d+\.\d[^ ]*", htext)
+ if m_version:
+ version = m_version.group()
+ yield version, text
+
+def update_release_note(package, version, text):
+ """Update the release notes for one version of a package."""
+ url = f"https://api.tidelift.com/external-api/lifting/{package}/release-notes/{version}"
+ token_file = os.path.join(os.path.dirname(__file__), "tidelift.token")
+ with open(token_file) as ftoken:
+ token = ftoken.read().strip()
+ headers = {
+ "Authorization": f"Bearer: {token}",
+ }
+ req_args = dict(url=url, data=text.encode('utf8'), headers=headers)
+ result = requests.post(**req_args)
+ if result.status_code == 409:
+ result = requests.put(**req_args)
+ print(f"{version}: {result.status_code}")
+
+def parse_and_upload(md_filename, package):
+ """Main function: parse markdown and upload to Tidelift."""
+ with open(md_filename) as f:
+ markdown = f.read()
+ for version, text in relnotes(markdown.splitlines(True)):
+ update_release_note(package, version, text)
+
+if __name__ == "__main__":
+ parse_and_upload(*sys.argv[1:]) # pylint: disable=no-value-for-parameter
diff --git a/third_party/python/coverage/coverage/__init__.py b/third_party/python/coverage/coverage/__init__.py
new file mode 100644
index 0000000000..331b304b68
--- /dev/null
+++ b/third_party/python/coverage/coverage/__init__.py
@@ -0,0 +1,36 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Code coverage measurement for Python.
+
+Ned Batchelder
+https://nedbatchelder.com/code/coverage
+
+"""
+
+import sys
+
+from coverage.version import __version__, __url__, version_info
+
+from coverage.control import Coverage, process_startup
+from coverage.data import CoverageData
+from coverage.misc import CoverageException
+from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
+from coverage.pytracer import PyTracer
+
+# Backward compatibility.
+coverage = Coverage
+
+# On Windows, we encode and decode deep enough that something goes wrong and
+# the encodings.utf_8 module is loaded and then unloaded, I don't know why.
+# Adding a reference here prevents it from being unloaded. Yuk.
+import encodings.utf_8 # pylint: disable=wrong-import-position, wrong-import-order
+
+# Because of the "from coverage.control import fooey" lines at the top of the
+# file, there's an entry for coverage.coverage in sys.modules, mapped to None.
+# This makes some inspection tools (like pydoc) unable to find the class
+# coverage.coverage. So remove that entry.
+try:
+ del sys.modules['coverage.coverage']
+except KeyError:
+ pass
diff --git a/third_party/python/coverage/coverage/__main__.py b/third_party/python/coverage/coverage/__main__.py
new file mode 100644
index 0000000000..79aa4e2b35
--- /dev/null
+++ b/third_party/python/coverage/coverage/__main__.py
@@ -0,0 +1,8 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Coverage.py's main entry point."""
+
+import sys
+from coverage.cmdline import main
+sys.exit(main())
diff --git a/third_party/python/coverage/coverage/annotate.py b/third_party/python/coverage/coverage/annotate.py
new file mode 100644
index 0000000000..999ab6e557
--- /dev/null
+++ b/third_party/python/coverage/coverage/annotate.py
@@ -0,0 +1,108 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Source file annotation for coverage.py."""
+
+import io
+import os
+import re
+
+from coverage.files import flat_rootname
+from coverage.misc import ensure_dir, isolate_module
+from coverage.report import get_analysis_to_report
+
+os = isolate_module(os)
+
+
+class AnnotateReporter(object):
+ """Generate annotated source files showing line coverage.
+
+ This reporter creates annotated copies of the measured source files. Each
+ .py file is copied as a .py,cover file, with a left-hand margin annotating
+ each line::
+
+ > def h(x):
+ - if 0: #pragma: no cover
+ - pass
+ > if x == 1:
+ ! a = 1
+ > else:
+ > a = 2
+
+ > h(2)
+
+ Executed lines use '>', lines not executed use '!', lines excluded from
+ consideration use '-'.
+
+ """
+
+ def __init__(self, coverage):
+ self.coverage = coverage
+ self.config = self.coverage.config
+ self.directory = None
+
+ blank_re = re.compile(r"\s*(#|$)")
+ else_re = re.compile(r"\s*else\s*:\s*(#|$)")
+
+ def report(self, morfs, directory=None):
+ """Run the report.
+
+ See `coverage.report()` for arguments.
+
+ """
+ self.directory = directory
+ self.coverage.get_data()
+ for fr, analysis in get_analysis_to_report(self.coverage, morfs):
+ self.annotate_file(fr, analysis)
+
+ def annotate_file(self, fr, analysis):
+ """Annotate a single file.
+
+ `fr` is the FileReporter for the file to annotate.
+
+ """
+ statements = sorted(analysis.statements)
+ missing = sorted(analysis.missing)
+ excluded = sorted(analysis.excluded)
+
+ if self.directory:
+ ensure_dir(self.directory)
+ dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename()))
+ if dest_file.endswith("_py"):
+ dest_file = dest_file[:-3] + ".py"
+ dest_file += ",cover"
+ else:
+ dest_file = fr.filename + ",cover"
+
+ with io.open(dest_file, 'w', encoding='utf8') as dest:
+ i = 0
+ j = 0
+ covered = True
+ source = fr.source()
+ for lineno, line in enumerate(source.splitlines(True), start=1):
+ while i < len(statements) and statements[i] < lineno:
+ i += 1
+ while j < len(missing) and missing[j] < lineno:
+ j += 1
+ if i < len(statements) and statements[i] == lineno:
+ covered = j >= len(missing) or missing[j] > lineno
+ if self.blank_re.match(line):
+ dest.write(u' ')
+ elif self.else_re.match(line):
+ # Special logic for lines containing only 'else:'.
+ if i >= len(statements) and j >= len(missing):
+ dest.write(u'! ')
+ elif i >= len(statements) or j >= len(missing):
+ dest.write(u'> ')
+ elif statements[i] == missing[j]:
+ dest.write(u'! ')
+ else:
+ dest.write(u'> ')
+ elif lineno in excluded:
+ dest.write(u'- ')
+ elif covered:
+ dest.write(u'> ')
+ else:
+ dest.write(u'! ')
+
+ dest.write(line)
diff --git a/third_party/python/coverage/coverage/backunittest.py b/third_party/python/coverage/coverage/backunittest.py
new file mode 100644
index 0000000000..078f48ccac
--- /dev/null
+++ b/third_party/python/coverage/coverage/backunittest.py
@@ -0,0 +1,33 @@
+# 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=arguments-differ, 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)
diff --git a/third_party/python/coverage/coverage/backward.py b/third_party/python/coverage/coverage/backward.py
new file mode 100644
index 0000000000..37b4916761
--- /dev/null
+++ b/third_party/python/coverage/coverage/backward.py
@@ -0,0 +1,253 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Add things to old Pythons so I can pretend they are newer."""
+
+# This file's purpose is to provide modules to be imported from here.
+# pylint: disable=unused-import
+
+import os
+import sys
+
+from coverage import env
+
+
+# Pythons 2 and 3 differ on where to get StringIO.
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+# In py3, ConfigParser was renamed to the more-standard configparser.
+# But there's a py3 backport that installs "configparser" in py2, and I don't
+# want it because it has annoying deprecation warnings. So try the real py2
+# import first.
+try:
+ import ConfigParser as configparser
+except ImportError:
+ import configparser
+
+# What's a string called?
+try:
+ string_class = basestring
+except NameError:
+ string_class = str
+
+# What's a Unicode string called?
+try:
+ unicode_class = unicode
+except NameError:
+ unicode_class = str
+
+# range or xrange?
+try:
+ range = xrange # pylint: disable=redefined-builtin
+except NameError:
+ range = range
+
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
+
+# Where do we get the thread id from?
+try:
+ from thread import get_ident as get_thread_id
+except ImportError:
+ from threading import get_ident as get_thread_id
+
+try:
+ os.PathLike
+except AttributeError:
+ # This is Python 2 and 3
+ path_types = (bytes, string_class, unicode_class)
+else:
+ # 3.6+
+ path_types = (bytes, str, os.PathLike)
+
+# shlex.quote is new, but there's an undocumented implementation in "pipes",
+# who knew!?
+try:
+ from shlex import quote as shlex_quote
+except ImportError:
+ # Useful function, available under a different (undocumented) name
+ # in Python versions earlier than 3.3.
+ from pipes import quote as shlex_quote
+
+try:
+ import reprlib
+except ImportError:
+ import repr as reprlib
+
+# A function to iterate listlessly over a dict's items, and one to get the
+# items as a list.
+try:
+ {}.iteritems
+except AttributeError:
+ # Python 3
+ def iitems(d):
+ """Produce the items from dict `d`."""
+ return d.items()
+
+ def litems(d):
+ """Return a list of items from dict `d`."""
+ return list(d.items())
+else:
+ # Python 2
+ def iitems(d):
+ """Produce the items from dict `d`."""
+ return d.iteritems()
+
+ def litems(d):
+ """Return a list of items from dict `d`."""
+ return d.items()
+
+# Getting the `next` function from an iterator is different in 2 and 3.
+try:
+ iter([]).next
+except AttributeError:
+ def iternext(seq):
+ """Get the `next` function for iterating over `seq`."""
+ return iter(seq).__next__
+else:
+ def iternext(seq):
+ """Get the `next` function for iterating over `seq`."""
+ return iter(seq).next
+
+# Python 3.x is picky about bytes and strings, so provide methods to
+# get them right, and make them no-ops in 2.x
+if env.PY3:
+ def to_bytes(s):
+ """Convert string `s` to bytes."""
+ return s.encode('utf8')
+
+ def to_string(b):
+ """Convert bytes `b` to string."""
+ return b.decode('utf8')
+
+ def binary_bytes(byte_values):
+ """Produce a byte string with the ints from `byte_values`."""
+ return bytes(byte_values)
+
+ def byte_to_int(byte):
+ """Turn a byte indexed from a bytes object into an int."""
+ return byte
+
+ def bytes_to_ints(bytes_value):
+ """Turn a bytes object into a sequence of ints."""
+ # In Python 3, iterating bytes gives ints.
+ return bytes_value
+
+else:
+ def to_bytes(s):
+ """Convert string `s` to bytes (no-op in 2.x)."""
+ return s
+
+ def to_string(b):
+ """Convert bytes `b` to string."""
+ return b
+
+ def binary_bytes(byte_values):
+ """Produce a byte string with the ints from `byte_values`."""
+ return "".join(chr(b) for b in byte_values)
+
+ def byte_to_int(byte):
+ """Turn a byte indexed from a bytes object into an int."""
+ return ord(byte)
+
+ def bytes_to_ints(bytes_value):
+ """Turn a bytes object into a sequence of ints."""
+ for byte in bytes_value:
+ yield ord(byte)
+
+
+try:
+ # In Python 2.x, the builtins were in __builtin__
+ BUILTINS = sys.modules['__builtin__']
+except KeyError:
+ # In Python 3.x, they're in builtins
+ BUILTINS = sys.modules['builtins']
+
+
+# imp was deprecated in Python 3.3
+try:
+ import importlib
+ import importlib.util
+ imp = None
+except ImportError:
+ importlib = None
+
+# We only want to use importlib if it has everything we need.
+try:
+ importlib_util_find_spec = importlib.util.find_spec
+except Exception:
+ import imp
+ importlib_util_find_spec = None
+
+# What is the .pyc magic number for this version of Python?
+try:
+ PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
+except AttributeError:
+ PYC_MAGIC_NUMBER = imp.get_magic()
+
+
+def code_object(fn):
+ """Get the code object from a function."""
+ try:
+ return fn.func_code
+ except AttributeError:
+ return fn.__code__
+
+
+try:
+ from types import SimpleNamespace
+except ImportError:
+ # The code from https://docs.python.org/3/library/types.html#types.SimpleNamespace
+ class SimpleNamespace:
+ """Python implementation of SimpleNamespace, for Python 2."""
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
+ def __repr__(self):
+ keys = sorted(self.__dict__)
+ 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 invalidate_import_caches():
+ """Invalidate any import caches that may or may not exist."""
+ if importlib and hasattr(importlib, "invalidate_caches"):
+ importlib.invalidate_caches()
+
+
+def import_local_file(modname, modfile=None):
+ """Import a local file as a module.
+
+ Opens a file in the current directory named `modname`.py, imports it
+ as `modname`, and returns the module object. `modfile` is the file to
+ import if it isn't in the current directory.
+
+ """
+ try:
+ from importlib.machinery import SourceFileLoader
+ except ImportError:
+ SourceFileLoader = None
+
+ if modfile is None:
+ modfile = modname + '.py'
+ if SourceFileLoader:
+ # pylint: disable=no-value-for-parameter, deprecated-method
+ mod = SourceFileLoader(modname, modfile).load_module()
+ else:
+ for suff in imp.get_suffixes(): # pragma: part covered
+ if suff[0] == '.py':
+ break
+
+ with open(modfile, 'r') as f:
+ # pylint: disable=undefined-loop-variable
+ mod = imp.load_module(modname, f, modfile, suff)
+
+ return mod
diff --git a/third_party/python/coverage/coverage/bytecode.py b/third_party/python/coverage/coverage/bytecode.py
new file mode 100644
index 0000000000..ceb18cf374
--- /dev/null
+++ b/third_party/python/coverage/coverage/bytecode.py
@@ -0,0 +1,19 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Bytecode manipulation for coverage.py"""
+
+import types
+
+
+def code_objects(code):
+ """Iterate over all the code objects in `code`."""
+ stack = [code]
+ while stack:
+ # We're going to return the code object on the stack, but first
+ # push its children for later returning.
+ code = stack.pop()
+ for c in code.co_consts:
+ if isinstance(c, types.CodeType):
+ stack.append(c)
+ yield code
diff --git a/third_party/python/coverage/coverage/cmdline.py b/third_party/python/coverage/coverage/cmdline.py
new file mode 100644
index 0000000000..9fddb6bb85
--- /dev/null
+++ b/third_party/python/coverage/coverage/cmdline.py
@@ -0,0 +1,866 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Command-line support for coverage.py."""
+
+from __future__ import print_function
+
+import glob
+import optparse
+import os.path
+import shlex
+import sys
+import textwrap
+import traceback
+
+import coverage
+from coverage import Coverage
+from coverage import env
+from coverage.collector import CTracer
+from coverage.data import line_counts
+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
+
+
+class Opts(object):
+ """A namespace class for individual options we'll build parsers from."""
+
+ append = optparse.make_option(
+ '-a', '--append', action='store_true',
+ help="Append coverage data to .coverage, otherwise it starts clean each time.",
+ )
+ branch = optparse.make_option(
+ '', '--branch', action='store_true',
+ help="Measure branch coverage in addition to statement coverage.",
+ )
+ CONCURRENCY_CHOICES = [
+ "thread", "gevent", "greenlet", "eventlet", "multiprocessing",
+ ]
+ concurrency = optparse.make_option(
+ '', '--concurrency', action='store', metavar="LIB",
+ choices=CONCURRENCY_CHOICES,
+ help=(
+ "Properly measure code using a concurrency library. "
+ "Valid values are: %s."
+ ) % ", ".join(CONCURRENCY_CHOICES),
+ )
+ context = optparse.make_option(
+ '', '--context', action='store', metavar="LABEL",
+ help="The context label to record for this coverage run.",
+ )
+ debug = optparse.make_option(
+ '', '--debug', action='store', metavar="OPTS",
+ help="Debug options, separated by commas. [env: COVERAGE_DEBUG]",
+ )
+ directory = optparse.make_option(
+ '-d', '--directory', action='store', metavar="DIR",
+ help="Write the output files to DIR.",
+ )
+ fail_under = optparse.make_option(
+ '', '--fail-under', action='store', metavar="MIN", type="float",
+ help="Exit with a status of 2 if the total coverage is less than MIN.",
+ )
+ help = optparse.make_option(
+ '-h', '--help', action='store_true',
+ help="Get help on this command.",
+ )
+ ignore_errors = optparse.make_option(
+ '-i', '--ignore-errors', action='store_true',
+ help="Ignore errors while reading source files.",
+ )
+ include = optparse.make_option(
+ '', '--include', action='store',
+ metavar="PAT1,PAT2,...",
+ help=(
+ "Include only files whose paths match one of these patterns. "
+ "Accepts shell-style wildcards, which must be quoted."
+ ),
+ )
+ pylib = optparse.make_option(
+ '-L', '--pylib', action='store_true',
+ help=(
+ "Measure coverage even inside the Python installed library, "
+ "which isn't done by default."
+ ),
+ )
+ show_missing = optparse.make_option(
+ '-m', '--show-missing', action='store_true',
+ help="Show line numbers of statements in each module that weren't executed.",
+ )
+ skip_covered = optparse.make_option(
+ '--skip-covered', action='store_true',
+ help="Skip files with 100% coverage.",
+ )
+ skip_empty = optparse.make_option(
+ '--skip-empty', action='store_true',
+ help="Skip files with no code.",
+ )
+ show_contexts = optparse.make_option(
+ '--show-contexts', action='store_true',
+ help="Show contexts for covered lines.",
+ )
+ omit = optparse.make_option(
+ '', '--omit', action='store',
+ metavar="PAT1,PAT2,...",
+ help=(
+ "Omit files whose paths match one of these patterns. "
+ "Accepts shell-style wildcards, which must be quoted."
+ ),
+ )
+ contexts = optparse.make_option(
+ '', '--contexts', action='store',
+ metavar="REGEX1,REGEX2,...",
+ help=(
+ "Only display data from lines covered in the given contexts. "
+ "Accepts Python regexes, which must be quoted."
+ ),
+ )
+ output_xml = optparse.make_option(
+ '-o', '', action='store', dest="outfile",
+ metavar="OUTFILE",
+ help="Write the XML report to this file. Defaults to 'coverage.xml'",
+ )
+ output_json = optparse.make_option(
+ '-o', '', action='store', dest="outfile",
+ metavar="OUTFILE",
+ help="Write the JSON report to this file. Defaults to 'coverage.json'",
+ )
+ json_pretty_print = optparse.make_option(
+ '', '--pretty-print', action='store_true',
+ help="Format the JSON for human readers.",
+ )
+ parallel_mode = optparse.make_option(
+ '-p', '--parallel-mode', action='store_true',
+ help=(
+ "Append the machine name, process id and random number to the "
+ ".coverage data file name to simplify collecting data from "
+ "many processes."
+ ),
+ )
+ module = optparse.make_option(
+ '-m', '--module', action='store_true',
+ help=(
+ "<pyfile> is an importable Python module, not a script path, "
+ "to be run as 'python -m' would run it."
+ ),
+ )
+ rcfile = optparse.make_option(
+ '', '--rcfile', action='store',
+ help=(
+ "Specify configuration file. "
+ "By default '.coveragerc', 'setup.cfg', 'tox.ini', and "
+ "'pyproject.toml' are tried. [env: COVERAGE_RCFILE]"
+ ),
+ )
+ source = optparse.make_option(
+ '', '--source', action='store', metavar="SRC1,SRC2,...",
+ help="A list of packages or directories of code to be measured.",
+ )
+ timid = optparse.make_option(
+ '', '--timid', action='store_true',
+ help=(
+ "Use a simpler but slower trace method. Try this if you get "
+ "seemingly impossible results!"
+ ),
+ )
+ title = optparse.make_option(
+ '', '--title', action='store', metavar="TITLE",
+ help="A text string to use as the title on the HTML.",
+ )
+ version = optparse.make_option(
+ '', '--version', action='store_true',
+ help="Display version information and exit.",
+ )
+
+
+class CoverageOptionParser(optparse.OptionParser, object):
+ """Base OptionParser for coverage.py.
+
+ Problems don't exit the program.
+ Defaults are initialized for all options.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(CoverageOptionParser, self).__init__(
+ add_help_option=False, *args, **kwargs
+ )
+ self.set_defaults(
+ action=None,
+ append=None,
+ branch=None,
+ concurrency=None,
+ context=None,
+ debug=None,
+ directory=None,
+ fail_under=None,
+ help=None,
+ ignore_errors=None,
+ include=None,
+ module=None,
+ omit=None,
+ contexts=None,
+ parallel_mode=None,
+ pylib=None,
+ rcfile=True,
+ show_missing=None,
+ skip_covered=None,
+ skip_empty=None,
+ show_contexts=None,
+ source=None,
+ timid=None,
+ title=None,
+ version=None,
+ )
+
+ self.disable_interspersed_args()
+
+ class OptionParserError(Exception):
+ """Used to stop the optparse error handler ending the process."""
+ pass
+
+ def parse_args_ok(self, args=None, options=None):
+ """Call optparse.parse_args, but return a triple:
+
+ (ok, options, args)
+
+ """
+ try:
+ options, args = super(CoverageOptionParser, self).parse_args(args, options)
+ except self.OptionParserError:
+ return False, None, None
+ return True, options, args
+
+ def error(self, msg):
+ """Override optparse.error so sys.exit doesn't get called."""
+ show_help(msg)
+ raise self.OptionParserError
+
+
+class GlobalOptionParser(CoverageOptionParser):
+ """Command-line parser for coverage.py global option arguments."""
+
+ def __init__(self):
+ super(GlobalOptionParser, self).__init__()
+
+ self.add_options([
+ Opts.help,
+ Opts.version,
+ ])
+
+
+class CmdOptionParser(CoverageOptionParser):
+ """Parse one of the new-style commands for coverage.py."""
+
+ def __init__(self, action, options, defaults=None, usage=None, description=None):
+ """Create an OptionParser for a coverage.py command.
+
+ `action` is the slug to put into `options.action`.
+ `options` is a list of Option's for the command.
+ `defaults` is a dict of default value for options.
+ `usage` is the usage string to display in help.
+ `description` is the description of the command, for the help text.
+
+ """
+ if usage:
+ usage = "%prog " + usage
+ super(CmdOptionParser, self).__init__(
+ usage=usage,
+ description=description,
+ )
+ self.set_defaults(action=action, **(defaults or {}))
+ self.add_options(options)
+ self.cmd = action
+
+ def __eq__(self, other):
+ # A convenience equality, so that I can put strings in unit test
+ # results, and they will compare equal to objects.
+ return (other == "<CmdOptionParser:%s>" % self.cmd)
+
+ __hash__ = None # This object doesn't need to be hashed.
+
+ def get_prog_name(self):
+ """Override of an undocumented function in optparse.OptionParser."""
+ program_name = super(CmdOptionParser, self).get_prog_name()
+
+ # Include the sub-command for this parser as part of the command.
+ return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd)
+
+
+GLOBAL_ARGS = [
+ Opts.debug,
+ Opts.help,
+ Opts.rcfile,
+ ]
+
+CMDS = {
+ 'annotate': CmdOptionParser(
+ "annotate",
+ [
+ Opts.directory,
+ Opts.ignore_errors,
+ Opts.include,
+ Opts.omit,
+ ] + GLOBAL_ARGS,
+ usage="[options] [modules]",
+ description=(
+ "Make annotated copies of the given files, marking statements that are executed "
+ "with > and statements that are missed with !."
+ ),
+ ),
+
+ 'combine': CmdOptionParser(
+ "combine",
+ [
+ Opts.append,
+ ] + GLOBAL_ARGS,
+ usage="[options] <path1> <path2> ... <pathN>",
+ description=(
+ "Combine data from multiple coverage files collected "
+ "with 'run -p'. The combined results are written to a single "
+ "file representing the union of the data. The positional "
+ "arguments are data files or directories containing data files. "
+ "If no paths are provided, data files in the default data file's "
+ "directory are combined."
+ ),
+ ),
+
+ 'debug': CmdOptionParser(
+ "debug", GLOBAL_ARGS,
+ usage="<topic>",
+ description=(
+ "Display information on 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."
+ ),
+ ),
+
+ 'erase': CmdOptionParser(
+ "erase", GLOBAL_ARGS,
+ description="Erase previously collected coverage data.",
+ ),
+
+ 'help': CmdOptionParser(
+ "help", GLOBAL_ARGS,
+ usage="[command]",
+ description="Describe how to use coverage.py",
+ ),
+
+ 'html': CmdOptionParser(
+ "html",
+ [
+ Opts.contexts,
+ Opts.directory,
+ Opts.fail_under,
+ Opts.ignore_errors,
+ Opts.include,
+ Opts.omit,
+ Opts.show_contexts,
+ Opts.skip_covered,
+ Opts.skip_empty,
+ Opts.title,
+ ] + GLOBAL_ARGS,
+ usage="[options] [modules]",
+ description=(
+ "Create an HTML report of the coverage of the files. "
+ "Each file gets its own page, with the source decorated to show "
+ "executed, excluded, and missed lines."
+ ),
+ ),
+
+ 'json': CmdOptionParser(
+ "json",
+ [
+ Opts.contexts,
+ Opts.fail_under,
+ Opts.ignore_errors,
+ Opts.include,
+ Opts.omit,
+ Opts.output_json,
+ Opts.json_pretty_print,
+ Opts.show_contexts,
+ ] + GLOBAL_ARGS,
+ usage="[options] [modules]",
+ description="Generate a JSON report of coverage results."
+ ),
+
+ 'report': CmdOptionParser(
+ "report",
+ [
+ Opts.contexts,
+ Opts.fail_under,
+ Opts.ignore_errors,
+ Opts.include,
+ Opts.omit,
+ Opts.show_missing,
+ Opts.skip_covered,
+ Opts.skip_empty,
+ ] + GLOBAL_ARGS,
+ usage="[options] [modules]",
+ description="Report coverage statistics on modules."
+ ),
+
+ 'run': CmdOptionParser(
+ "run",
+ [
+ Opts.append,
+ Opts.branch,
+ Opts.concurrency,
+ Opts.context,
+ Opts.include,
+ Opts.module,
+ Opts.omit,
+ Opts.pylib,
+ Opts.parallel_mode,
+ Opts.source,
+ Opts.timid,
+ ] + GLOBAL_ARGS,
+ usage="[options] <pyfile> [program options]",
+ description="Run a Python program, measuring code execution."
+ ),
+
+ 'xml': CmdOptionParser(
+ "xml",
+ [
+ Opts.fail_under,
+ Opts.ignore_errors,
+ Opts.include,
+ Opts.omit,
+ Opts.output_xml,
+ ] + GLOBAL_ARGS,
+ usage="[options] [modules]",
+ description="Generate an XML report of coverage results."
+ ),
+}
+
+
+def show_help(error=None, topic=None, parser=None):
+ """Display an error message, or the named topic."""
+ assert error or topic or parser
+
+ program_path = sys.argv[0]
+ if program_path.endswith(os.path.sep + '__main__.py'):
+ # The path is the main module of a package; get that path instead.
+ program_path = os.path.dirname(program_path)
+ program_name = os.path.basename(program_path)
+ if env.WINDOWS:
+ # entry_points={'console_scripts':...} on Windows makes files
+ # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
+ # invoke coverage-script.py, coverage3-script.py, and
+ # coverage-3.5-script.py. argv[0] is the .py file, but we want to
+ # get back to the original form.
+ auto_suffix = "-script.py"
+ if program_name.endswith(auto_suffix):
+ program_name = program_name[:-len(auto_suffix)]
+
+ help_params = dict(coverage.__dict__)
+ help_params['program_name'] = program_name
+ if CTracer is not None:
+ help_params['extension_modifier'] = 'with C extension'
+ else:
+ help_params['extension_modifier'] = 'without C extension'
+
+ if error:
+ print(error, file=sys.stderr)
+ print("Use '%s help' for help." % (program_name,), file=sys.stderr)
+ elif parser:
+ print(parser.format_help().strip())
+ print()
+ else:
+ help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
+ if help_msg:
+ print(help_msg.format(**help_params))
+ else:
+ print("Don't know topic %r" % topic)
+ print("Full documentation is at {__url__}".format(**help_params))
+
+
+OK, ERR, FAIL_UNDER = 0, 1, 2
+
+
+class CoverageScript(object):
+ """The command-line interface to coverage.py."""
+
+ def __init__(self):
+ self.global_option = False
+ self.coverage = None
+
+ def command_line(self, argv):
+ """The bulk of the command line interface to coverage.py.
+
+ `argv` is the argument list to process.
+
+ Returns 0 if all is well, 1 if something went wrong.
+
+ """
+ # Collect the command-line options.
+ if not argv:
+ show_help(topic='minimum_help')
+ return OK
+
+ # The command syntax we parse depends on the first argument. Global
+ # switch syntax always starts with an option.
+ self.global_option = argv[0].startswith('-')
+ if self.global_option:
+ parser = GlobalOptionParser()
+ else:
+ parser = CMDS.get(argv[0])
+ if not parser:
+ show_help("Unknown command: '%s'" % argv[0])
+ return ERR
+ argv = argv[1:]
+
+ ok, options, args = parser.parse_args_ok(argv)
+ if not ok:
+ return ERR
+
+ # Handle help and version.
+ if self.do_help(options, args, parser):
+ return OK
+
+ # Listify the list options.
+ source = unshell_list(options.source)
+ omit = unshell_list(options.omit)
+ include = unshell_list(options.include)
+ debug = unshell_list(options.debug)
+ contexts = unshell_list(options.contexts)
+
+ # Do something.
+ self.coverage = Coverage(
+ data_suffix=options.parallel_mode,
+ cover_pylib=options.pylib,
+ timid=options.timid,
+ branch=options.branch,
+ config_file=options.rcfile,
+ source=source,
+ omit=omit,
+ include=include,
+ debug=debug,
+ concurrency=options.concurrency,
+ check_preimported=True,
+ context=options.context,
+ )
+
+ if options.action == "debug":
+ return self.do_debug(args)
+
+ elif options.action == "erase":
+ self.coverage.erase()
+ return OK
+
+ elif options.action == "run":
+ return self.do_run(options, args)
+
+ elif options.action == "combine":
+ if options.append:
+ self.coverage.load()
+ data_dirs = args or None
+ self.coverage.combine(data_dirs, strict=True)
+ self.coverage.save()
+ return OK
+
+ # Remaining actions are reporting, with some common options.
+ report_args = dict(
+ morfs=unglob_args(args),
+ ignore_errors=options.ignore_errors,
+ omit=omit,
+ include=include,
+ contexts=contexts,
+ )
+
+ # We need to be able to import from the current directory, because
+ # plugins may try to, for example, to read Django settings.
+ sys.path.insert(0, '')
+
+ self.coverage.load()
+
+ total = None
+ if options.action == "report":
+ total = self.coverage.report(
+ show_missing=options.show_missing,
+ skip_covered=options.skip_covered,
+ skip_empty=options.skip_empty,
+ **report_args
+ )
+ elif options.action == "annotate":
+ self.coverage.annotate(directory=options.directory, **report_args)
+ elif options.action == "html":
+ total = self.coverage.html_report(
+ directory=options.directory,
+ title=options.title,
+ skip_covered=options.skip_covered,
+ skip_empty=options.skip_empty,
+ show_contexts=options.show_contexts,
+ **report_args
+ )
+ elif options.action == "xml":
+ outfile = options.outfile
+ total = self.coverage.xml_report(outfile=outfile, **report_args)
+ elif options.action == "json":
+ outfile = options.outfile
+ total = self.coverage.json_report(
+ outfile=outfile,
+ pretty_print=options.pretty_print,
+ show_contexts=options.show_contexts,
+ **report_args
+ )
+
+ if total is not None:
+ # Apply the command line fail-under options, and then use the config
+ # value, so we can get fail_under from the config file.
+ if options.fail_under is not None:
+ self.coverage.set_option("report:fail_under", options.fail_under)
+
+ fail_under = self.coverage.get_option("report:fail_under")
+ precision = self.coverage.get_option("report:precision")
+ if should_fail_under(total, fail_under, precision):
+ return FAIL_UNDER
+
+ return OK
+
+ def do_help(self, options, args, parser):
+ """Deal with help requests.
+
+ Return True if it handled the request, False if not.
+
+ """
+ # Handle help.
+ if options.help:
+ if self.global_option:
+ show_help(topic='help')
+ else:
+ show_help(parser=parser)
+ return True
+
+ if options.action == "help":
+ if args:
+ for a in args:
+ parser = CMDS.get(a)
+ if parser:
+ show_help(parser=parser)
+ else:
+ show_help(topic=a)
+ else:
+ show_help(topic='help')
+ return True
+
+ # Handle version.
+ if options.version:
+ show_help(topic='version')
+ return True
+
+ return False
+
+ def do_run(self, options, args):
+ """Implementation of 'coverage run'."""
+
+ if not args:
+ if options.module:
+ # Specified -m with nothing else.
+ show_help("No module specified for -m")
+ return ERR
+ command_line = self.coverage.get_option("run:command_line")
+ if command_line is not None:
+ args = shlex.split(command_line)
+ if args and args[0] == "-m":
+ options.module = True
+ args = args[1:]
+ if not args:
+ show_help("Nothing to do.")
+ return ERR
+
+ if options.append and self.coverage.get_option("run:parallel"):
+ show_help("Can't append to data files in parallel mode.")
+ return ERR
+
+ if options.concurrency == "multiprocessing":
+ # Can't set other run-affecting command line options with
+ # multiprocessing.
+ for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']:
+ # As it happens, all of these options have no default, meaning
+ # they will be None if they have not been specified.
+ if getattr(options, opt_name) is not None:
+ show_help(
+ "Options affecting multiprocessing must only be specified "
+ "in a configuration file.\n"
+ "Remove --{} from the command line.".format(opt_name)
+ )
+ return ERR
+
+ runner = PyRunner(args, as_module=bool(options.module))
+ runner.prepare()
+
+ if options.append:
+ self.coverage.load()
+
+ # Run the script.
+ self.coverage.start()
+ code_ran = True
+ try:
+ runner.run()
+ except NoSource:
+ code_ran = False
+ raise
+ finally:
+ self.coverage.stop()
+ if code_ran:
+ self.coverage.save()
+
+ return OK
+
+ def do_debug(self, args):
+ """Implementation of 'coverage debug'."""
+
+ if not args:
+ show_help("What information would you like: config, data, sys, premain?")
+ return ERR
+
+ for info in args:
+ if info == 'sys':
+ sys_info = self.coverage.sys_info()
+ print(info_header("sys"))
+ for line in info_formatter(sys_info):
+ print(" %s" % line)
+ elif info == 'data':
+ self.coverage.load()
+ data = self.coverage.get_data()
+ print(info_header("data"))
+ print("path: %s" % self.coverage.get_data().data_filename())
+ if data:
+ print("has_arcs: %r" % data.has_arcs())
+ summary = line_counts(data, fullpath=True)
+ filenames = sorted(summary.keys())
+ print("\n%d files:" % len(filenames))
+ for f in filenames:
+ line = "%s: %d lines" % (f, summary[f])
+ plugin = data.file_tracer(f)
+ if plugin:
+ line += " [%s]" % plugin
+ print(line)
+ else:
+ print("No data collected")
+ elif info == 'config':
+ print(info_header("config"))
+ config_info = self.coverage.config.__dict__.items()
+ for line in info_formatter(config_info):
+ print(" %s" % line)
+ elif info == "premain":
+ print(info_header("premain"))
+ print(short_stack())
+ else:
+ show_help("Don't know what you mean by %r" % info)
+ return ERR
+
+ return OK
+
+
+def unshell_list(s):
+ """Turn a command-line argument into a list."""
+ if not s:
+ return None
+ if env.WINDOWS:
+ # When running coverage.py as coverage.exe, some of the behavior
+ # of the shell is emulated: wildcards are expanded into a list of
+ # file names. So you have to single-quote patterns on the command
+ # line, but (not) helpfully, the single quotes are included in the
+ # argument, so we have to strip them off here.
+ s = s.strip("'")
+ return s.split(',')
+
+
+def unglob_args(args):
+ """Interpret shell wildcards for platforms that need it."""
+ if env.WINDOWS:
+ globbed = []
+ for arg in args:
+ if '?' in arg or '*' in arg:
+ globbed.extend(glob.glob(arg))
+ else:
+ globbed.append(arg)
+ args = globbed
+ return args
+
+
+HELP_TOPICS = {
+ 'help': """\
+ Coverage.py, version {__version__} {extension_modifier}
+ Measure, collect, and report on code coverage in Python programs.
+
+ usage: {program_name} <command> [options] [args]
+
+ Commands:
+ annotate Annotate source files with execution information.
+ combine Combine a number of data files.
+ erase Erase previously collected coverage data.
+ help Get help on using coverage.py.
+ html Create an HTML report.
+ json Create a JSON report of coverage results.
+ report Report coverage stats on modules.
+ run Run a Python program and measure code execution.
+ xml Create an XML report of coverage results.
+
+ Use "{program_name} help <command>" for detailed help on any command.
+ """,
+
+ 'minimum_help': """\
+ Code coverage for Python. Use '{program_name} help' for help.
+ """,
+
+ 'version': """\
+ Coverage.py, version {__version__} {extension_modifier}
+ """,
+}
+
+
+def main(argv=None):
+ """The main entry point to coverage.py.
+
+ This is installed as the script entry point.
+
+ """
+ if argv is None:
+ argv = sys.argv[1:]
+ try:
+ status = CoverageScript().command_line(argv)
+ except ExceptionDuringRun as err:
+ # An exception was caught while running the product code. The
+ # sys.exc_info() return tuple is packed into an ExceptionDuringRun
+ # exception.
+ traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
+ status = ERR
+ except BaseCoverageException as err:
+ # A controlled error inside coverage.py: print the message to the user.
+ msg = err.args[0]
+ if env.PY2:
+ msg = msg.encode(output_encoding())
+ print(msg)
+ status = ERR
+ except SystemExit as err:
+ # The user called `sys.exit()`. Exit with their argument, if any.
+ if err.args:
+ status = err.args[0]
+ else:
+ status = None
+ return status
+
+# Profiling using ox_profile. Install it from GitHub:
+# pip install git+https://github.com/emin63/ox_profile.git
+#
+# $set_env.py: COVERAGE_PROFILE - Set to use ox_profile.
+_profile = os.environ.get("COVERAGE_PROFILE", "")
+if _profile: # pragma: debugging
+ from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
+ original_main = main
+
+ def main(argv=None): # pylint: disable=function-redefined
+ """A wrapper around main that profiles."""
+ try:
+ profiler = SimpleLauncher.launch()
+ return original_main(argv)
+ finally:
+ data, _ = profiler.query(re_filter='coverage', max_records=100)
+ print(profiler.show(query=data, limit=100, sep='', col=''))
+ profiler.cancel()
diff --git a/third_party/python/coverage/coverage/collector.py b/third_party/python/coverage/coverage/collector.py
new file mode 100644
index 0000000000..a042357f67
--- /dev/null
+++ b/third_party/python/coverage/coverage/collector.py
@@ -0,0 +1,429 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Raw data collector for coverage.py."""
+
+import os
+import sys
+
+from coverage import env
+from coverage.backward import litems, range # pylint: disable=redefined-builtin
+from coverage.debug import short_stack
+from coverage.disposition import FileDisposition
+from coverage.misc import CoverageException, isolate_module
+from coverage.pytracer import PyTracer
+
+os = isolate_module(os)
+
+
+try:
+ # Use the C extension code when we can, for speed.
+ from coverage.tracer import CTracer, CFileDisposition
+except ImportError:
+ # Couldn't import the C extension, maybe it isn't built.
+ if os.getenv('COVERAGE_TEST_TRACER') == 'c':
+ # During testing, we use the COVERAGE_TEST_TRACER environment variable
+ # to indicate that we've fiddled with the environment to test this
+ # fallback code. If we thought we had a C tracer, but couldn't import
+ # it, then exit quickly and clearly instead of dribbling confusing
+ # errors. I'm using sys.exit here instead of an exception because an
+ # exception here causes all sorts of other noise in unittest.
+ sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n")
+ sys.exit(1)
+ CTracer = None
+
+
+class Collector(object):
+ """Collects trace data.
+
+ Creates a Tracer object for each thread, since they track stack
+ information. Each Tracer points to the same shared data, contributing
+ traced data points.
+
+ When the Collector is started, it creates a Tracer for the current thread,
+ and installs a function to create Tracers for each new thread started.
+ When the Collector is stopped, all active Tracers are stopped.
+
+ Threads started while the Collector is stopped will never have Tracers
+ associated with them.
+
+ """
+
+ # The stack of active Collectors. Collectors are added here when started,
+ # and popped when stopped. Collectors on the stack are paused when not
+ # the top, and resumed when they become the top again.
+ _collectors = []
+
+ # The concurrency settings we support here.
+ SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"])
+
+ def __init__(
+ self, should_trace, check_include, should_start_context, file_mapper,
+ timid, branch, warn, concurrency,
+ ):
+ """Create a collector.
+
+ `should_trace` is a function, taking a file name and a frame, and
+ returning a `coverage.FileDisposition object`.
+
+ `check_include` is a function taking a file name and a frame. It returns
+ a boolean: True if the file should be traced, False if not.
+
+ `should_start_context` is a function taking a frame, and returning a
+ string. If the frame should be the start of a new context, the string
+ is the new context. If the frame should not be the start of a new
+ context, return None.
+
+ `file_mapper` is a function taking a filename, and returning a Unicode
+ filename. The result is the name that will be recorded in the data
+ file.
+
+ If `timid` is true, then a slower simpler trace function will be
+ used. This is important for some environments where manipulation of
+ tracing functions make the faster more sophisticated trace function not
+ operate properly.
+
+ If `branch` is true, then branches will be measured. This involves
+ collecting data on which statements followed each other (arcs). Use
+ `get_arc_data` to get the arc data.
+
+ `warn` is a warning function, taking a single string message argument
+ and an optional slug argument which will be a string or None, to be
+ used if a warning needs to be issued.
+
+ `concurrency` is a list of strings indicating the concurrency libraries
+ in use. Valid values are "greenlet", "eventlet", "gevent", or "thread"
+ (the default). Of these four values, only one can be supplied. Other
+ values are ignored.
+
+ """
+ self.should_trace = should_trace
+ self.check_include = check_include
+ self.should_start_context = should_start_context
+ self.file_mapper = file_mapper
+ self.warn = warn
+ self.branch = branch
+ self.threading = None
+ self.covdata = None
+
+ self.static_context = None
+
+ self.origin = short_stack()
+
+ self.concur_id_func = None
+ self.mapped_file_cache = {}
+
+ # We can handle a few concurrency options here, but only one at a time.
+ these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
+ if len(these_concurrencies) > 1:
+ raise CoverageException("Conflicting concurrency settings: %s" % concurrency)
+ self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
+
+ try:
+ if self.concurrency == "greenlet":
+ import greenlet
+ self.concur_id_func = greenlet.getcurrent
+ elif self.concurrency == "eventlet":
+ import eventlet.greenthread # pylint: disable=import-error,useless-suppression
+ self.concur_id_func = eventlet.greenthread.getcurrent
+ elif self.concurrency == "gevent":
+ import gevent # pylint: disable=import-error,useless-suppression
+ self.concur_id_func = gevent.getcurrent
+ elif self.concurrency == "thread" or not self.concurrency:
+ # It's important to import threading only if we need it. If
+ # it's imported early, and the program being measured uses
+ # gevent, then gevent's monkey-patching won't work properly.
+ import threading
+ self.threading = threading
+ else:
+ raise CoverageException("Don't understand concurrency=%s" % concurrency)
+ except ImportError:
+ raise CoverageException(
+ "Couldn't trace with concurrency=%s, the module isn't installed." % (
+ self.concurrency,
+ )
+ )
+
+ self.reset()
+
+ if timid:
+ # Being timid: use the simple Python trace function.
+ self._trace_class = PyTracer
+ else:
+ # Being fast: use the C Tracer if it is available, else the Python
+ # trace function.
+ self._trace_class = CTracer or PyTracer
+
+ if self._trace_class is CTracer:
+ self.file_disposition_class = CFileDisposition
+ self.supports_plugins = True
+ else:
+ self.file_disposition_class = FileDisposition
+ self.supports_plugins = False
+
+ def __repr__(self):
+ return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name())
+
+ def use_data(self, covdata, context):
+ """Use `covdata` for recording data."""
+ self.covdata = covdata
+ self.static_context = context
+ self.covdata.set_context(self.static_context)
+
+ def tracer_name(self):
+ """Return the class name of the tracer we're using."""
+ return self._trace_class.__name__
+
+ def _clear_data(self):
+ """Clear out existing data, but stay ready for more collection."""
+ # We used to used self.data.clear(), but that would remove filename
+ # keys and data values that were still in use higher up the stack
+ # when we are called as part of switch_context.
+ for d in self.data.values():
+ d.clear()
+
+ for tracer in self.tracers:
+ tracer.reset_activity()
+
+ def reset(self):
+ """Clear collected data, and prepare to collect more."""
+ # A dictionary mapping file names to dicts with line number keys (if not
+ # branch coverage), or mapping file names to dicts with line number
+ # pairs as keys (if branch coverage).
+ self.data = {}
+
+ # A dictionary mapping file names to file tracer plugin names that will
+ # handle them.
+ self.file_tracers = {}
+
+ # 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
+ # Coverage.should_trace. Its .trace attribute indicates whether the
+ # file should be traced or not. If it should be, a plugin with dynamic
+ # file names can decide not to trace it based on the dynamic file name
+ # being excluded by the inclusion rules, in which case the
+ # FileDisposition will be replaced by None in the cache.
+ if env.PYPY:
+ import __pypy__ # pylint: disable=import-error
+ # Alex Gaynor said:
+ # should_trace_cache is a strictly growing key: once a key is in
+ # it, it never changes. Further, the keys used to access it are
+ # generally constant, given sufficient context. That is to say, at
+ # any given point _trace() is called, pypy is able to know the key.
+ # This is because the key is determined by the physical source code
+ # line, and that's invariant with the call site.
+ #
+ # This property of a dict with immutable keys, combined with
+ # call-site-constant keys is a match for PyPy's module dict,
+ # which is optimized for such workloads.
+ #
+ # This gives a 20% benefit on the workload described at
+ # https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage
+ self.should_trace_cache = __pypy__.newdict("module")
+ else:
+ self.should_trace_cache = {}
+
+ # Our active Tracers.
+ self.tracers = []
+
+ self._clear_data()
+
+ def _start_tracer(self):
+ """Start a new Tracer object, and store it in self.tracers."""
+ tracer = self._trace_class()
+ tracer.data = self.data
+ tracer.trace_arcs = self.branch
+ tracer.should_trace = self.should_trace
+ tracer.should_trace_cache = self.should_trace_cache
+ tracer.warn = self.warn
+
+ if hasattr(tracer, 'concur_id_func'):
+ tracer.concur_id_func = self.concur_id_func
+ elif self.concur_id_func:
+ raise CoverageException(
+ "Can't support concurrency=%s with %s, only threads are supported" % (
+ self.concurrency, self.tracer_name(),
+ )
+ )
+
+ if hasattr(tracer, 'file_tracers'):
+ tracer.file_tracers = self.file_tracers
+ if hasattr(tracer, 'threading'):
+ tracer.threading = self.threading
+ if hasattr(tracer, 'check_include'):
+ tracer.check_include = self.check_include
+ if hasattr(tracer, 'should_start_context'):
+ tracer.should_start_context = self.should_start_context
+ tracer.switch_context = self.switch_context
+
+ fn = tracer.start()
+ self.tracers.append(tracer)
+
+ return fn
+
+ # The trace function has to be set individually on each thread before
+ # execution begins. Ironically, the only support the threading module has
+ # for running code before the thread main is the tracing function. So we
+ # install this as a trace function, and the first time it's called, it does
+ # the real trace installation.
+
+ def _installation_trace(self, frame, event, arg):
+ """Called on new threads, installs the real tracer."""
+ # Remove ourselves as the trace function.
+ sys.settrace(None)
+ # Install the real tracer.
+ fn = self._start_tracer()
+ # Invoke the real trace function with the current event, to be sure
+ # not to lose an event.
+ if fn:
+ fn = fn(frame, event, arg)
+ # Return the new trace function to continue tracing in this scope.
+ return fn
+
+ def start(self):
+ """Start collecting trace information."""
+ if self._collectors:
+ self._collectors[-1].pause()
+
+ self.tracers = []
+
+ # Check to see whether we had a fullcoverage tracer installed. If so,
+ # get the stack frames it stashed away for us.
+ traces0 = []
+ fn0 = sys.gettrace()
+ if fn0:
+ tracer0 = getattr(fn0, '__self__', None)
+ if tracer0:
+ traces0 = getattr(tracer0, 'traces', [])
+
+ try:
+ # Install the tracer on this thread.
+ fn = self._start_tracer()
+ except:
+ if self._collectors:
+ self._collectors[-1].resume()
+ raise
+
+ # If _start_tracer succeeded, then we add ourselves to the global
+ # stack of collectors.
+ self._collectors.append(self)
+
+ # Replay all the events from fullcoverage into the new trace function.
+ for args in traces0:
+ (frame, event, arg), lineno = args
+ try:
+ fn(frame, event, arg, lineno=lineno)
+ except TypeError:
+ raise Exception("fullcoverage must be run with the C trace function.")
+
+ # Install our installation tracer in threading, to jump-start other
+ # threads.
+ if self.threading:
+ self.threading.settrace(self._installation_trace)
+
+ def stop(self):
+ """Stop collecting trace information."""
+ assert self._collectors
+ if self._collectors[-1] is not self:
+ print("self._collectors:")
+ for c in self._collectors:
+ print(" {!r}\n{}".format(c, c.origin))
+ assert self._collectors[-1] is self, (
+ "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1])
+ )
+
+ self.pause()
+
+ # Remove this Collector from the stack, and resume the one underneath
+ # (if any).
+ self._collectors.pop()
+ if self._collectors:
+ self._collectors[-1].resume()
+
+ def pause(self):
+ """Pause tracing, but be prepared to `resume`."""
+ for tracer in self.tracers:
+ tracer.stop()
+ stats = tracer.get_stats()
+ if stats:
+ print("\nCoverage.py tracer stats:")
+ for k in sorted(stats.keys()):
+ print("%20s: %s" % (k, stats[k]))
+ if self.threading:
+ self.threading.settrace(None)
+
+ def resume(self):
+ """Resume tracing after a `pause`."""
+ for tracer in self.tracers:
+ tracer.start()
+ if self.threading:
+ self.threading.settrace(self._installation_trace)
+ else:
+ self._start_tracer()
+
+ def _activity(self):
+ """Has any activity been traced?
+
+ Returns a boolean, True if any trace function was invoked.
+
+ """
+ return any(tracer.activity() for tracer in self.tracers)
+
+ def switch_context(self, new_context):
+ """Switch to a new dynamic context."""
+ self.flush_data()
+ if self.static_context:
+ context = self.static_context
+ if new_context:
+ context += "|" + new_context
+ else:
+ context = new_context
+ self.covdata.set_context(context)
+
+ def cached_mapped_file(self, filename):
+ """A locally cached version of file names mapped through file_mapper."""
+ key = (type(filename), filename)
+ try:
+ return self.mapped_file_cache[key]
+ except KeyError:
+ return self.mapped_file_cache.setdefault(key, self.file_mapper(filename))
+
+ def mapped_file_dict(self, d):
+ """Return a dict like d, but with keys modified by file_mapper."""
+ # The call to litems() ensures that the GIL protects the dictionary
+ # iterator against concurrent modifications by tracers running
+ # in other threads. We try three times in case of concurrent
+ # access, hoping to get a clean copy.
+ runtime_err = None
+ for _ in range(3):
+ try:
+ items = litems(d)
+ except RuntimeError as ex:
+ runtime_err = ex
+ else:
+ break
+ else:
+ raise runtime_err
+
+ return dict((self.cached_mapped_file(k), v) for k, v in items if v)
+
+ def flush_data(self):
+ """Save the collected data to our associated `CoverageData`.
+
+ Data may have also been saved along the way. This forces the
+ last of the data to be saved.
+
+ Returns True if there was data to save, False if not.
+ """
+ if not self._activity():
+ return False
+
+ if self.branch:
+ 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))
+
+ self._clear_data()
+ return True
diff --git a/third_party/python/coverage/coverage/config.py b/third_party/python/coverage/coverage/config.py
new file mode 100644
index 0000000000..7876052b50
--- /dev/null
+++ b/third_party/python/coverage/coverage/config.py
@@ -0,0 +1,555 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Config file for coverage.py"""
+
+import collections
+import copy
+import os
+import os.path
+import re
+
+from coverage import env
+from coverage.backward import configparser, iitems, string_class
+from coverage.misc import contract, CoverageException, isolate_module
+from coverage.misc import substitute_variables
+
+from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
+
+os = isolate_module(os)
+
+
+class HandyConfigParser(configparser.RawConfigParser):
+ """Our specialization of ConfigParser."""
+
+ def __init__(self, our_file):
+ """Create the HandyConfigParser.
+
+ `our_file` is True if this config file is specifically for coverage,
+ False if we are examining another config file (tox.ini, setup.cfg)
+ for possible settings.
+ """
+
+ configparser.RawConfigParser.__init__(self)
+ self.section_prefixes = ["coverage:"]
+ if our_file:
+ self.section_prefixes.append("")
+
+ def read(self, filenames, encoding=None):
+ """Read a file name as UTF-8 configuration data."""
+ kwargs = {}
+ if env.PYVERSION >= (3, 2):
+ kwargs['encoding'] = encoding or "utf-8"
+ return configparser.RawConfigParser.read(self, filenames, **kwargs)
+
+ def has_option(self, section, option):
+ for section_prefix in self.section_prefixes:
+ real_section = section_prefix + section
+ has = configparser.RawConfigParser.has_option(self, real_section, option)
+ if has:
+ return has
+ return False
+
+ def has_section(self, section):
+ for section_prefix in self.section_prefixes:
+ real_section = section_prefix + section
+ has = configparser.RawConfigParser.has_section(self, real_section)
+ if has:
+ return real_section
+ return False
+
+ def options(self, section):
+ for section_prefix in self.section_prefixes:
+ real_section = section_prefix + section
+ if configparser.RawConfigParser.has_section(self, real_section):
+ return configparser.RawConfigParser.options(self, real_section)
+ raise configparser.NoSectionError
+
+ def get_section(self, section):
+ """Get the contents of a section, as a dictionary."""
+ d = {}
+ for opt in self.options(section):
+ d[opt] = self.get(section, opt)
+ return d
+
+ def get(self, section, option, *args, **kwargs): # pylint: disable=arguments-differ
+ """Get a value, replacing environment variables also.
+
+ The arguments are the same as `RawConfigParser.get`, but in the found
+ value, ``$WORD`` or ``${WORD}`` are replaced by the value of the
+ environment variable ``WORD``.
+
+ Returns the finished value.
+
+ """
+ for section_prefix in self.section_prefixes:
+ real_section = section_prefix + section
+ if configparser.RawConfigParser.has_option(self, real_section, option):
+ break
+ else:
+ raise configparser.NoOptionError
+
+ v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
+ v = substitute_variables(v, os.environ)
+ return v
+
+ def getlist(self, section, option):
+ """Read a list of strings.
+
+ The value of `section` and `option` is treated as a comma- and newline-
+ separated list of strings. Each value is stripped of whitespace.
+
+ Returns the list of strings.
+
+ """
+ value_list = self.get(section, option)
+ values = []
+ for value_line in value_list.split('\n'):
+ for value in value_line.split(','):
+ value = value.strip()
+ if value:
+ values.append(value)
+ return values
+
+ def getregexlist(self, section, option):
+ """Read a list of full-line regexes.
+
+ The value of `section` and `option` is treated as a newline-separated
+ list of regexes. Each value is stripped of whitespace.
+
+ Returns the list of strings.
+
+ """
+ line_list = self.get(section, option)
+ value_list = []
+ for value in line_list.splitlines():
+ value = value.strip()
+ try:
+ re.compile(value)
+ except re.error as e:
+ raise CoverageException(
+ "Invalid [%s].%s value %r: %s" % (section, option, value, e)
+ )
+ if value:
+ value_list.append(value)
+ return value_list
+
+
+# The default line exclusion regexes.
+DEFAULT_EXCLUDE = [
+ r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)',
+]
+
+# The default partial branch regexes, to be modified by the user.
+DEFAULT_PARTIAL = [
+ r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)',
+]
+
+# The default partial branch regexes, based on Python semantics.
+# These are any Python branching constructs that can't actually execute all
+# their branches.
+DEFAULT_PARTIAL_ALWAYS = [
+ 'while (True|1|False|0):',
+ 'if (True|1|False|0):',
+]
+
+
+class CoverageConfig(object):
+ """Coverage.py configuration.
+
+ The attributes of this class are the various settings that control the
+ operation of coverage.py.
+
+ """
+ # pylint: disable=too-many-instance-attributes
+
+ def __init__(self):
+ """Initialize the configuration attributes to their defaults."""
+ # Metadata about the config.
+ # We tried to read these config files.
+ self.attempted_config_files = []
+ # We did read these config files, but maybe didn't find any content for us.
+ self.config_files_read = []
+ # The file that gave us our configuration.
+ self.config_file = None
+ self._config_contents = None
+
+ # Defaults for [run] and [report]
+ self._include = None
+ self._omit = None
+
+ # Defaults for [run]
+ self.branch = False
+ self.command_line = None
+ self.concurrency = None
+ self.context = None
+ self.cover_pylib = False
+ self.data_file = ".coverage"
+ self.debug = []
+ self.disable_warnings = []
+ self.dynamic_context = None
+ self.note = None
+ self.parallel = False
+ self.plugins = []
+ self.relative_files = False
+ self.run_include = None
+ self.run_omit = None
+ self.source = None
+ self.timid = False
+ self._crash = None
+
+ # Defaults for [report]
+ self.exclude_list = DEFAULT_EXCLUDE[:]
+ self.fail_under = 0.0
+ self.ignore_errors = False
+ self.report_include = None
+ self.report_omit = None
+ self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
+ self.partial_list = DEFAULT_PARTIAL[:]
+ self.precision = 0
+ self.report_contexts = None
+ self.show_missing = False
+ self.skip_covered = False
+ self.skip_empty = False
+
+ # Defaults for [html]
+ self.extra_css = None
+ self.html_dir = "htmlcov"
+ self.html_title = "Coverage report"
+ self.show_contexts = False
+
+ # Defaults for [xml]
+ self.xml_output = "coverage.xml"
+ self.xml_package_depth = 99
+
+ # Defaults for [json]
+ self.json_output = "coverage.json"
+ self.json_pretty_print = False
+ self.json_show_contexts = False
+
+ # Defaults for [paths]
+ self.paths = collections.OrderedDict()
+
+ # Options for plugins
+ self.plugin_options = {}
+
+ MUST_BE_LIST = [
+ "debug", "concurrency", "plugins",
+ "report_omit", "report_include",
+ "run_omit", "run_include",
+ ]
+
+ def from_args(self, **kwargs):
+ """Read config values from `kwargs`."""
+ for k, v in iitems(kwargs):
+ if v is not None:
+ if k in self.MUST_BE_LIST and isinstance(v, string_class):
+ v = [v]
+ setattr(self, k, v)
+
+ @contract(filename=str)
+ def from_file(self, filename, our_file):
+ """Read configuration from a .rc file.
+
+ `filename` is a file name to read.
+
+ `our_file` is True if this config file is specifically for coverage,
+ False if we are examining another config file (tox.ini, setup.cfg)
+ for possible settings.
+
+ Returns True or False, whether the file could be read, and it had some
+ coverage.py settings in it.
+
+ """
+ _, ext = os.path.splitext(filename)
+ if ext == '.toml':
+ cp = TomlConfigParser(our_file)
+ else:
+ cp = HandyConfigParser(our_file)
+
+ self.attempted_config_files.append(filename)
+
+ try:
+ files_read = cp.read(filename)
+ except (configparser.Error, TomlDecodeError) as err:
+ raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
+ if not files_read:
+ return False
+
+ self.config_files_read.extend(map(os.path.abspath, files_read))
+
+ any_set = False
+ try:
+ for option_spec in self.CONFIG_FILE_OPTIONS:
+ was_set = self._set_attr_from_config_option(cp, *option_spec)
+ if was_set:
+ any_set = True
+ except ValueError as err:
+ raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
+
+ # Check that there are no unrecognized options.
+ all_options = collections.defaultdict(set)
+ for option_spec in self.CONFIG_FILE_OPTIONS:
+ section, option = option_spec[1].split(":")
+ all_options[section].add(option)
+
+ for section, options in iitems(all_options):
+ real_section = cp.has_section(section)
+ if real_section:
+ for unknown in set(cp.options(section)) - options:
+ raise CoverageException(
+ "Unrecognized option '[%s] %s=' in config file %s" % (
+ real_section, unknown, filename
+ )
+ )
+
+ # [paths] is special
+ if cp.has_section('paths'):
+ for option in cp.options('paths'):
+ self.paths[option] = cp.getlist('paths', option)
+ any_set = True
+
+ # plugins can have options
+ for plugin in self.plugins:
+ if cp.has_section(plugin):
+ self.plugin_options[plugin] = cp.get_section(plugin)
+ any_set = True
+
+ # Was this file used as a config file? If it's specifically our file,
+ # then it was used. If we're piggybacking on someone else's file,
+ # then it was only used if we found some settings in it.
+ if our_file:
+ used = True
+ else:
+ used = any_set
+
+ if used:
+ self.config_file = os.path.abspath(filename)
+ with open(filename) as f:
+ self._config_contents = f.read()
+
+ return used
+
+ def copy(self):
+ """Return a copy of the configuration."""
+ return copy.deepcopy(self)
+
+ CONFIG_FILE_OPTIONS = [
+ # These are *args for _set_attr_from_config_option:
+ # (attr, where, type_="")
+ #
+ # attr is the attribute to set on the CoverageConfig object.
+ # where is the section:name to read from the configuration file.
+ # type_ is the optional type to apply, by using .getTYPE to read the
+ # configuration value from the file.
+
+ # [run]
+ ('branch', 'run:branch', 'boolean'),
+ ('command_line', 'run:command_line'),
+ ('concurrency', 'run:concurrency', 'list'),
+ ('context', 'run:context'),
+ ('cover_pylib', 'run:cover_pylib', 'boolean'),
+ ('data_file', 'run:data_file'),
+ ('debug', 'run:debug', 'list'),
+ ('disable_warnings', 'run:disable_warnings', 'list'),
+ ('dynamic_context', 'run:dynamic_context'),
+ ('note', 'run:note'),
+ ('parallel', 'run:parallel', 'boolean'),
+ ('plugins', 'run:plugins', 'list'),
+ ('relative_files', 'run:relative_files', 'boolean'),
+ ('run_include', 'run:include', 'list'),
+ ('run_omit', 'run:omit', 'list'),
+ ('source', 'run:source', 'list'),
+ ('timid', 'run:timid', 'boolean'),
+ ('_crash', 'run:_crash'),
+
+ # [report]
+ ('exclude_list', 'report:exclude_lines', 'regexlist'),
+ ('fail_under', 'report:fail_under', 'float'),
+ ('ignore_errors', 'report:ignore_errors', 'boolean'),
+ ('partial_always_list', 'report:partial_branches_always', 'regexlist'),
+ ('partial_list', 'report:partial_branches', 'regexlist'),
+ ('precision', 'report:precision', 'int'),
+ ('report_contexts', 'report:contexts', 'list'),
+ ('report_include', 'report:include', 'list'),
+ ('report_omit', 'report:omit', 'list'),
+ ('show_missing', 'report:show_missing', 'boolean'),
+ ('skip_covered', 'report:skip_covered', 'boolean'),
+ ('skip_empty', 'report:skip_empty', 'boolean'),
+ ('sort', 'report:sort'),
+
+ # [html]
+ ('extra_css', 'html:extra_css'),
+ ('html_dir', 'html:directory'),
+ ('html_title', 'html:title'),
+ ('show_contexts', 'html:show_contexts', 'boolean'),
+
+ # [xml]
+ ('xml_output', 'xml:output'),
+ ('xml_package_depth', 'xml:package_depth', 'int'),
+
+ # [json]
+ ('json_output', 'json:output'),
+ ('json_pretty_print', 'json:pretty_print', 'boolean'),
+ ('json_show_contexts', 'json:show_contexts', 'boolean'),
+ ]
+
+ def _set_attr_from_config_option(self, cp, attr, where, type_=''):
+ """Set an attribute on self if it exists in the ConfigParser.
+
+ Returns True if the attribute was set.
+
+ """
+ section, option = where.split(":")
+ if cp.has_option(section, option):
+ method = getattr(cp, 'get' + type_)
+ setattr(self, attr, method(section, option))
+ return True
+ return False
+
+ def get_plugin_options(self, plugin):
+ """Get a dictionary of options for the plugin named `plugin`."""
+ return self.plugin_options.get(plugin, {})
+
+ def set_option(self, option_name, value):
+ """Set an option in the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ option name. For example, the ``branch`` option in the ``[run]``
+ section of the config file would be indicated with `"run:branch"`.
+
+ `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:
+ attr, where = option_spec[:2]
+ if where == option_name:
+ setattr(self, attr, value)
+ return
+
+ # See if it's a plugin option.
+ plugin_name, _, key = option_name.partition(":")
+ if key and plugin_name in self.plugins:
+ self.plugin_options.setdefault(plugin_name, {})[key] = value
+ return
+
+ # If we get here, we didn't find the option.
+ raise CoverageException("No such option: %r" % option_name)
+
+ def get_option(self, option_name):
+ """Get an option from the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ 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.
+
+ """
+ # 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]
+ if where == option_name:
+ return getattr(self, attr)
+
+ # See if it's a plugin option.
+ plugin_name, _, key = option_name.partition(":")
+ if key and plugin_name in self.plugins:
+ return self.plugin_options.get(plugin_name, {}).get(key)
+
+ # If we get here, we didn't find the option.
+ raise CoverageException("No such option: %r" % option_name)
+
+
+def config_files_to_try(config_file):
+ """What config files should we try to read?
+
+ Returns a list of tuples:
+ (filename, is_our_file, was_file_specified)
+ """
+
+ # Some API users were specifying ".coveragerc" to mean the same as
+ # True, so make it so.
+ if config_file == ".coveragerc":
+ config_file = True
+ specified_file = (config_file is not True)
+ if not specified_file:
+ # No file was specified. Check COVERAGE_RCFILE.
+ config_file = os.environ.get('COVERAGE_RCFILE')
+ if config_file:
+ specified_file = True
+ if not specified_file:
+ # Still no file specified. Default to .coveragerc
+ config_file = ".coveragerc"
+ files_to_try = [
+ (config_file, True, specified_file),
+ ("setup.cfg", False, False),
+ ("tox.ini", False, False),
+ ("pyproject.toml", False, False),
+ ]
+ return files_to_try
+
+
+def read_coverage_config(config_file, **kwargs):
+ """Read the coverage.py configuration.
+
+ Arguments:
+ config_file: a boolean or string, see the `Coverage` class for the
+ tricky details.
+ all others: keyword arguments from the `Coverage` class, used for
+ setting values in the configuration.
+
+ Returns:
+ config:
+ config is a CoverageConfig object read from the appropriate
+ configuration file.
+
+ """
+ # Build the configuration from a number of sources:
+ # 1) defaults:
+ config = CoverageConfig()
+
+ # 2) from a file:
+ if config_file:
+ files_to_try = config_files_to_try(config_file)
+
+ for fname, our_file, specified_file in files_to_try:
+ config_read = config.from_file(fname, our_file=our_file)
+ if config_read:
+ break
+ if specified_file:
+ raise CoverageException("Couldn't read '%s' as a config file" % fname)
+
+ # $set_env.py: COVERAGE_DEBUG - Options for --debug.
+ # 3) from environment variables:
+ env_data_file = os.environ.get('COVERAGE_FILE')
+ if env_data_file:
+ config.data_file = env_data_file
+ debugs = os.environ.get('COVERAGE_DEBUG')
+ if debugs:
+ config.debug.extend(d.strip() for d in debugs.split(","))
+
+ # 4) from constructor arguments:
+ config.from_args(**kwargs)
+
+ # 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()
+ )
+
+ return config
diff --git a/third_party/python/coverage/coverage/context.py b/third_party/python/coverage/coverage/context.py
new file mode 100644
index 0000000000..ea13da21ed
--- /dev/null
+++ b/third_party/python/coverage/coverage/context.py
@@ -0,0 +1,91 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Determine contexts for coverage.py"""
+
+
+def combine_context_switchers(context_switchers):
+ """Create a single context switcher from multiple switchers.
+
+ `context_switchers` is a list of functions that take a frame as an
+ argument and return a string to use as the new context label.
+
+ Returns a function that composites `context_switchers` functions, or None
+ if `context_switchers` is an empty list.
+
+ When invoked, the combined switcher calls `context_switchers` one-by-one
+ until a string is returned. The combined switcher returns None if all
+ `context_switchers` return None.
+ """
+ if not context_switchers:
+ return None
+
+ if len(context_switchers) == 1:
+ return context_switchers[0]
+
+ def should_start_context(frame):
+ """The combiner for multiple context switchers."""
+ for switcher in context_switchers:
+ new_context = switcher(frame)
+ if new_context is not None:
+ return new_context
+ return None
+
+ return should_start_context
+
+
+def should_start_context_test_function(frame):
+ """Is this frame calling a test_* function?"""
+ co_name = frame.f_code.co_name
+ if co_name.startswith("test") or co_name == "runTest":
+ return qualname_from_frame(frame)
+ return None
+
+
+def qualname_from_frame(frame):
+ """Get a qualified name for the code running in `frame`."""
+ co = frame.f_code
+ fname = co.co_name
+ method = None
+ if co.co_argcount and co.co_varnames[0] == "self":
+ self = frame.f_locals["self"]
+ method = getattr(self, fname, None)
+
+ if method is None:
+ func = frame.f_globals.get(fname)
+ if func is None:
+ return None
+ return func.__module__ + '.' + fname
+
+ func = getattr(method, '__func__', None)
+ if func is None:
+ cls = self.__class__
+ return cls.__module__ + '.' + cls.__name__ + "." + fname
+
+ if hasattr(func, '__qualname__'):
+ qname = func.__module__ + '.' + func.__qualname__
+ else:
+ for cls in getattr(self.__class__, '__mro__', ()):
+ f = cls.__dict__.get(fname, None)
+ if f is None:
+ continue
+ if f is func:
+ qname = cls.__module__ + '.' + cls.__name__ + "." + fname
+ break
+ else:
+ # Support for old-style classes.
+ def mro(bases):
+ for base in bases:
+ f = base.__dict__.get(fname, None)
+ if f is func:
+ return base.__module__ + '.' + base.__name__ + "." + fname
+ for base in bases:
+ qname = mro(base.__bases__)
+ if qname is not None:
+ return qname
+ return None
+ qname = mro([self.__class__])
+ if qname is None:
+ qname = func.__module__ + '.' + fname
+
+ return qname
diff --git a/third_party/python/coverage/coverage/control.py b/third_party/python/coverage/coverage/control.py
new file mode 100644
index 0000000000..2b8c3d261d
--- /dev/null
+++ b/third_party/python/coverage/coverage/control.py
@@ -0,0 +1,1110 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Core control stuff for coverage.py."""
+
+import atexit
+import contextlib
+import os
+import os.path
+import platform
+import sys
+import time
+
+from coverage import env
+from coverage.annotate import AnnotateReporter
+from coverage.backward import string_class, iitems
+from coverage.collector import Collector, CTracer
+from coverage.config import read_coverage_config
+from coverage.context import should_start_context_test_function, combine_context_switchers
+from coverage.data import CoverageData, combine_parallel_data
+from coverage.debug import DebugControl, short_stack, write_formatted_info
+from coverage.disposition import disposition_debug_msg
+from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
+from coverage.html import HtmlReporter
+from coverage.inorout import InOrOut
+from coverage.jsonreport import JsonReporter
+from coverage.misc import CoverageException, bool_or_none, join_regex
+from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
+from coverage.plugin import FileReporter
+from coverage.plugin_support import Plugins
+from coverage.python import PythonFileReporter
+from coverage.report import render_report
+from coverage.results import Analysis, Numbers
+from coverage.summary import SummaryReporter
+from coverage.xmlreport import XmlReporter
+
+try:
+ from coverage.multiproc import patch_multiprocessing
+except ImportError: # pragma: only jython
+ # Jython has no multiprocessing module.
+ patch_multiprocessing = None
+
+os = isolate_module(os)
+
+@contextlib.contextmanager
+def override_config(cov, **kwargs):
+ """Temporarily tweak the configuration of `cov`.
+
+ The arguments are applied to `cov.config` with the `from_args` method.
+ At the end of the with-statement, the old configuration is restored.
+ """
+ original_config = cov.config
+ cov.config = cov.config.copy()
+ try:
+ cov.config.from_args(**kwargs)
+ yield
+ finally:
+ cov.config = original_config
+
+
+_DEFAULT_DATAFILE = DefaultValue("MISSING")
+
+class Coverage(object):
+ """Programmatic access to coverage.py.
+
+ To use::
+
+ from coverage import Coverage
+
+ cov = Coverage()
+ cov.start()
+ #.. call your code ..
+ cov.stop()
+ cov.html_report(directory='covhtml')
+
+ Note: in keeping with Python custom, names starting with underscore are
+ not part of the public API. They might stop working at any point. Please
+ limit yourself to documented methods to avoid problems.
+
+ """
+
+ # The stack of started Coverage instances.
+ _instances = []
+
+ @classmethod
+ def current(cls):
+ """Get the latest started `Coverage` instance, if any.
+
+ Returns: a `Coverage` instance, or None.
+
+ .. versionadded:: 5.0
+
+ """
+ if cls._instances:
+ return cls._instances[-1]
+ else:
+ return None
+
+ 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,
+ concurrency=None, check_preimported=False, context=None,
+ ):
+ """
+ Many of these arguments duplicate and override values that can be
+ provided in a configuration file. Parameters that are missing here
+ will use values from the config file.
+
+ `data_file` is the base name of the data file to use. The config value
+ defaults to ".coverage". None can be provided to prevent writing a data
+ file. `data_suffix` is appended (with a dot) to `data_file` to create
+ the final file name. If `data_suffix` is simply True, then a suffix is
+ created with the machine and process identity included.
+
+ `cover_pylib` is a boolean determining whether Python code installed
+ with the Python interpreter is measured. This includes the Python
+ standard library and any packages installed with the interpreter.
+
+ If `auto_data` is true, then any existing data file will be read when
+ coverage measurement starts, and data will be saved automatically when
+ measurement stops.
+
+ If `timid` is true, then a slower and simpler trace function will be
+ used. This is important for some environments where manipulation of
+ tracing functions breaks the faster trace function.
+
+ If `branch` is true, then branch coverage will be measured in addition
+ to the usual statement coverage.
+
+ `config_file` determines what configuration file to read:
+
+ * If it is ".coveragerc", it is interpreted as if it were True,
+ for backward compatibility.
+
+ * If it is a string, it is the name of the file to read. If the
+ file can't be read, it is an error.
+
+ * If it is True, then a few standard files names are tried
+ (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for
+ these files to not be found.
+
+ * If it is False, then no configuration file is read.
+
+ `source` is a list of file paths or package names. Only code located
+ in the trees indicated by the file paths or package names will be
+ measured.
+
+ `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.
+
+ `debug` is a list of strings indicating what debugging information is
+ desired.
+
+ `concurrency` is a string indicating the concurrency library being used
+ in the measured code. Without this, coverage.py will get incorrect
+ results if these libraries are in use. Valid strings are "greenlet",
+ "eventlet", "gevent", "multiprocessing", or "thread" (the default).
+ This can also be a list of these strings.
+
+ If `check_preimported` is true, then when coverage is started, the
+ already-imported files will be checked to see if they should be
+ measured by coverage. Importing measured files before coverage is
+ started can mean that code is missed.
+
+ `context` is a string to use as the :ref:`static context
+ <static_contexts>` label for collected data.
+
+ .. versionadded:: 4.0
+ The `concurrency` parameter.
+
+ .. versionadded:: 4.2
+ The `concurrency` parameter can now be a list of strings.
+
+ .. versionadded:: 5.0
+ The `check_preimported` and `context` parameters.
+
+ """
+ # data_file=None means no disk file at all. data_file missing means
+ # use the value from the config file.
+ self._no_disk = data_file is None
+ if data_file is _DEFAULT_DATAFILE:
+ data_file = None
+
+ # Build our configuration from a number of sources.
+ self.config = read_coverage_config(
+ 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,
+ report_omit=omit, report_include=include,
+ concurrency=concurrency, context=context,
+ )
+
+ # This is injectable by tests.
+ self._debug_file = None
+
+ self._auto_load = self._auto_save = auto_data
+ self._data_suffix_specified = data_suffix
+
+ # Is it ok for no data to be collected?
+ self._warn_no_data = True
+ self._warn_unimported_source = True
+ self._warn_preimported_source = check_preimported
+ self._no_warn_slugs = None
+
+ # A record of all the warnings that have been issued.
+ self._warnings = []
+
+ # Other instance attributes, set later.
+ 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
+ self._file_mapper = None
+
+ # State machine variables:
+ # Have we initialized everything?
+ self._inited = False
+ self._inited_for_start = False
+ # Have we started collecting and not stopped it?
+ self._started = False
+ # Should we write the debug output?
+ self._should_write_debug = True
+
+ # If we have sub-process measurement happening automatically, then we
+ # want any explicit creation of a Coverage object to mean, this process
+ # is already coverage-aware, so don't auto-measure it. By now, the
+ # auto-creation of a Coverage object has already happened. But we can
+ # find it and tell it not to save its data.
+ if not env.METACOV:
+ _prevent_sub_process_measurement()
+
+ def _init(self):
+ """Set all the initial state.
+
+ This is called by the public methods to initialize state. This lets us
+ construct a :class:`Coverage` object, then tweak its state before this
+ function is called.
+
+ """
+ if self._inited:
+ return
+
+ self._inited = True
+
+ # Create and configure the debugging controller. COVERAGE_DEBUG_FILE
+ # is an environment variable, the name of a file to append debug logs
+ # to.
+ self._debug = DebugControl(self.config.debug, self._debug_file)
+
+ if "multiprocessing" in (self.config.concurrency or ()):
+ # Multi-processing uses parallel for the subprocesses, so also use
+ # it for the main process.
+ self.config.parallel = True
+
+ # _exclude_re is a dict that maps exclusion list names to compiled regexes.
+ self._exclude_re = {}
+
+ set_relative_directory()
+ self._file_mapper = relative_filename if self.config.relative_files else abs_file
+
+ # Load plugins
+ self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug)
+
+ # Run configuring plugins.
+ for plugin in self._plugins.configurers:
+ # We need an object with set_option and get_option. Either self or
+ # self.config will do. Choosing randomly stops people from doing
+ # other things with those objects, against the public API. Yes,
+ # this is a bit childish. :)
+ plugin.configure([self, self.config][int(time.time()) % 2])
+
+ def _post_init(self):
+ """Stuff to do after everything is initialized."""
+ if self._should_write_debug:
+ self._should_write_debug = False
+ self._write_startup_debug()
+
+ # '[run] _crash' will raise an exception if the value is close by in
+ # the call stack, for testing error handling.
+ if self.config._crash and self.config._crash in short_stack(limit=4):
+ raise Exception("Crashing because called by {}".format(self.config._crash))
+
+ def _write_startup_debug(self):
+ """Write out debug info at startup if needed."""
+ wrote_any = False
+ with self._debug.without_callers():
+ if self._debug.should('config'):
+ config_info = sorted(self.config.__dict__.items())
+ config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
+ write_formatted_info(self._debug, "config", config_info)
+ wrote_any = True
+
+ if self._debug.should('sys'):
+ write_formatted_info(self._debug, "sys", self.sys_info())
+ for plugin in self._plugins:
+ header = "sys: " + plugin._coverage_plugin_name
+ info = plugin.sys_info()
+ write_formatted_info(self._debug, header, info)
+ wrote_any = True
+
+ if wrote_any:
+ write_formatted_info(self._debug, "end", ())
+
+ def _should_trace(self, filename, frame):
+ """Decide whether to trace execution in `filename`.
+
+ Calls `_should_trace_internal`, and returns the FileDisposition.
+
+ """
+ disp = self._inorout.should_trace(filename, frame)
+ if self._debug.should('trace'):
+ self._debug.write(disposition_debug_msg(disp))
+ return disp
+
+ def _check_include_omit_etc(self, filename, frame):
+ """Check a file name against the include/omit/etc, rules, verbosely.
+
+ Returns a boolean: True if the file should be traced, False if not.
+
+ """
+ reason = self._inorout.check_include_omit_etc(filename, frame)
+ if self._debug.should('trace'):
+ if not reason:
+ msg = "Including %r" % (filename,)
+ else:
+ msg = "Not including %r: %s" % (filename, reason)
+ self._debug.write(msg)
+
+ return not reason
+
+ def _warn(self, msg, slug=None, once=False):
+ """Use `msg` as a warning.
+
+ For warning suppression, use `slug` as the shorthand.
+
+ If `once` is true, only show this warning once (determined by the
+ slug.)
+
+ """
+ if self._no_warn_slugs is None:
+ self._no_warn_slugs = list(self.config.disable_warnings)
+
+ if slug in self._no_warn_slugs:
+ # Don't issue the warning
+ return
+
+ self._warnings.append(msg)
+ if slug:
+ msg = "%s (%s)" % (msg, slug)
+ if self._debug.should('pid'):
+ msg = "[%d] %s" % (os.getpid(), msg)
+ sys.stderr.write("Coverage.py warning: %s\n" % msg)
+
+ if once:
+ self._no_warn_slugs.append(slug)
+
+ def get_option(self, option_name):
+ """Get an option from the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ 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. 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
+
+ """
+ return self.config.get_option(option_name)
+
+ def set_option(self, option_name, value):
+ """Set an option in the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ option name. For example, the ``branch`` option in the ``[run]``
+ section of the config file would be indicated with ``"run:branch"``.
+
+ `value` is the new value for the option. This should be an
+ appropriate Python value. For example, use True for booleans, not the
+ string ``"True"``.
+
+ As an example, calling::
+
+ cov.set_option("run:branch", True)
+
+ has the same effect as this configuration file::
+
+ [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
+
+ """
+ self.config.set_option(option_name, value)
+
+ def load(self):
+ """Load previously-collected coverage data from the data file."""
+ self._init()
+ if self._collector:
+ self._collector.reset()
+ should_skip = self.config.parallel and not os.path.exists(self.config.data_file)
+ if not should_skip:
+ self._init_data(suffix=None)
+ self._post_init()
+ if not should_skip:
+ self._data.read()
+
+ def _init_for_start(self):
+ """Initialization for start()"""
+ # Construct the collector.
+ concurrency = self.config.concurrency or ()
+ if "multiprocessing" in concurrency:
+ if not patch_multiprocessing:
+ raise CoverageException( # pragma: only jython
+ "multiprocessing is not supported on this Python"
+ )
+ patch_multiprocessing(rcfile=self.config.config_file)
+
+ dycon = self.config.dynamic_context
+ if not dycon or dycon == "none":
+ context_switchers = []
+ elif dycon == "test_function":
+ context_switchers = [should_start_context_test_function]
+ else:
+ raise CoverageException(
+ "Don't understand dynamic_context setting: {!r}".format(dycon)
+ )
+
+ context_switchers.extend(
+ plugin.dynamic_context for plugin in self._plugins.context_switchers
+ )
+
+ should_start_context = combine_context_switchers(context_switchers)
+
+ self._collector = Collector(
+ should_trace=self._should_trace,
+ check_include=self._check_include_omit_etc,
+ should_start_context=should_start_context,
+ file_mapper=self._file_mapper,
+ timid=self.config.timid,
+ branch=self.config.branch,
+ warn=self._warn,
+ concurrency=concurrency,
+ )
+
+ suffix = self._data_suffix_specified
+ if suffix or self.config.parallel:
+ if not isinstance(suffix, string_class):
+ # if data_suffix=True, use .machinename.pid.random
+ suffix = True
+ else:
+ suffix = None
+
+ self._init_data(suffix)
+
+ self._collector.use_data(self._data, self.config.context)
+
+ # Early warning if we aren't going to be able to support plugins.
+ if self._plugins.file_tracers and not self._collector.supports_plugins:
+ self._warn(
+ "Plugin file tracers (%s) aren't supported with %s" % (
+ ", ".join(
+ plugin._coverage_plugin_name
+ for plugin in self._plugins.file_tracers
+ ),
+ self._collector.tracer_name(),
+ )
+ )
+ for plugin in self._plugins.file_tracers:
+ plugin._coverage_enabled = False
+
+ # Create the file classifying substructure.
+ self._inorout = self._inorout_class(warn=self._warn)
+ self._inorout.configure(self.config)
+ self._inorout.plugins = self._plugins
+ self._inorout.disp_class = self._collector.file_disposition_class
+
+ # It's useful to write debug info after initing for start.
+ self._should_write_debug = True
+
+ atexit.register(self._atexit)
+
+ def _init_data(self, suffix):
+ """Create a data file if we don't have one yet."""
+ if self._data is None:
+ # Create the data file. We do this at construction time so that the
+ # data file will be written into the directory where the process
+ # started rather than wherever the process eventually chdir'd to.
+ ensure_dir_for_file(self.config.data_file)
+ self._data = CoverageData(
+ basename=self.config.data_file,
+ suffix=suffix,
+ warn=self._warn,
+ debug=self._debug,
+ no_disk=self._no_disk,
+ )
+
+ def start(self):
+ """Start measuring code coverage.
+
+ Coverage measurement only occurs in functions called after
+ :meth:`start` is invoked. Statements in the same scope as
+ :meth:`start` won't be measured.
+
+ Once you invoke :meth:`start`, you must also call :meth:`stop`
+ eventually, or your process might not shut down cleanly.
+
+ """
+ self._init()
+ if not self._inited_for_start:
+ self._inited_for_start = True
+ self._init_for_start()
+ self._post_init()
+
+ # Issue warnings for possible problems.
+ self._inorout.warn_conflicting_settings()
+
+ # See if we think some code that would eventually be measured has
+ # already been imported.
+ if self._warn_preimported_source:
+ self._inorout.warn_already_imported_files()
+
+ if self._auto_load:
+ self.load()
+
+ self._collector.start()
+ self._started = True
+ self._instances.append(self)
+
+ def stop(self):
+ """Stop measuring code coverage."""
+ if self._instances:
+ if self._instances[-1] is self:
+ self._instances.pop()
+ if self._started:
+ self._collector.stop()
+ self._started = False
+
+ def _atexit(self):
+ """Clean up on process shutdown."""
+ if self._debug.should("process"):
+ self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self))
+ if self._started:
+ self.stop()
+ if self._auto_save:
+ self.save()
+
+ def erase(self):
+ """Erase previously collected coverage data.
+
+ This removes the in-memory data collected in this session as well as
+ discarding the data file.
+
+ """
+ self._init()
+ self._post_init()
+ if self._collector:
+ self._collector.reset()
+ self._init_data(suffix=None)
+ self._data.erase(parallel=self.config.parallel)
+ self._data = None
+ self._inited_for_start = False
+
+ def switch_context(self, new_context):
+ """Switch to a new dynamic context.
+
+ `new_context` is a string to use as the :ref:`dynamic context
+ <dynamic_contexts>` label for collected data. If a :ref:`static
+ context <static_contexts>` is in use, the static and dynamic context
+ labels will be joined together with a pipe character.
+
+ Coverage collection must be started already.
+
+ .. versionadded:: 5.0
+
+ """
+ if not self._started: # pragma: part started
+ raise CoverageException(
+ "Cannot switch context, coverage is not started"
+ )
+
+ if self._collector.should_start_context:
+ self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
+
+ self._collector.switch_context(new_context)
+
+ def clear_exclude(self, which='exclude'):
+ """Clear the exclude list."""
+ self._init()
+ setattr(self.config, which + "_list", [])
+ self._exclude_regex_stale()
+
+ def exclude(self, regex, which='exclude'):
+ """Exclude source lines from execution consideration.
+
+ A number of lists of regular expressions are maintained. Each list
+ selects lines that are treated differently during reporting.
+
+ `which` determines which list is modified. The "exclude" list selects
+ lines that are not considered executable at all. The "partial" list
+ indicates lines with branches that are not taken.
+
+ `regex` is a regular expression. The regex is added to the specified
+ list. If any of the regexes in the list is found in a line, the line
+ is marked for special treatment during reporting.
+
+ """
+ self._init()
+ excl_list = getattr(self.config, which + "_list")
+ excl_list.append(regex)
+ self._exclude_regex_stale()
+
+ def _exclude_regex_stale(self):
+ """Drop all the compiled exclusion regexes, a list was modified."""
+ self._exclude_re.clear()
+
+ def _exclude_regex(self, which):
+ """Return a compiled regex for the given exclusion list."""
+ if which not in self._exclude_re:
+ excl_list = getattr(self.config, which + "_list")
+ self._exclude_re[which] = join_regex(excl_list)
+ return self._exclude_re[which]
+
+ def get_exclude_list(self, which='exclude'):
+ """Return a list of excluded regex patterns.
+
+ `which` indicates which list is desired. See :meth:`exclude` for the
+ lists that are available, and their meaning.
+
+ """
+ self._init()
+ return getattr(self.config, which + "_list")
+
+ def save(self):
+ """Save the collected coverage data to the data file."""
+ data = self.get_data()
+ data.write()
+
+ def combine(self, data_paths=None, strict=False):
+ """Combine together a number of similarly-named coverage data files.
+
+ All coverage data files whose name starts with `data_file` (from the
+ coverage() constructor) will be read, and combined together into the
+ current measurements.
+
+ `data_paths` is a list of files or directories from which data should
+ be combined. If no list is passed, then the data files from the
+ directory indicated by the current data file (probably the current
+ directory) will be combined.
+
+ If `strict` is true, then it is an error to attempt to combine when
+ there are no data files to combine.
+
+ .. versionadded:: 4.0
+ The `data_paths` parameter.
+
+ .. versionadded:: 4.3
+ The `strict` parameter.
+
+ """
+ self._init()
+ self._init_data(suffix=None)
+ self._post_init()
+ self.get_data()
+
+ aliases = None
+ if self.config.paths:
+ aliases = PathAliases()
+ for paths in self.config.paths.values():
+ result = paths[0]
+ for pattern in paths[1:]:
+ aliases.add(pattern, result)
+
+ combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict)
+
+ def get_data(self):
+ """Get the collected data.
+
+ Also warn about various problems collecting data.
+
+ Returns a :class:`coverage.CoverageData`, the collected coverage data.
+
+ .. versionadded:: 4.0
+
+ """
+ self._init()
+ self._init_data(suffix=None)
+ self._post_init()
+
+ if self._collector and self._collector.flush_data():
+ self._post_save_work()
+
+ return self._data
+
+ def _post_save_work(self):
+ """After saving data, look for warnings, post-work, etc.
+
+ Warn about things that should have happened but didn't.
+ Look for unexecuted files.
+
+ """
+ # If there are still entries in the source_pkgs_unmatched list,
+ # then we never encountered those packages.
+ if self._warn_unimported_source:
+ self._inorout.warn_unimported_source()
+
+ # Find out if we got any data.
+ if not self._data and self._warn_no_data:
+ self._warn("No data was collected.", slug="no-data-collected")
+
+ # Touch all the files that could have executed, so that we can
+ # mark completely unexecuted files as 0% covered.
+ if self._data is not None:
+ for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files():
+ file_path = self._file_mapper(file_path)
+ self._data.touch_file(file_path, plugin_name)
+
+ if self.config.note:
+ self._warn("The '[run] note' setting is no longer supported.")
+
+ # Backward compatibility with version 1.
+ def analysis(self, morf):
+ """Like `analysis2` but doesn't return excluded line numbers."""
+ f, s, _, m, mf = self.analysis2(morf)
+ return f, s, m, mf
+
+ def analysis2(self, morf):
+ """Analyze a module.
+
+ `morf` is a module or a file name. It will be analyzed to determine
+ its coverage statistics. The return value is a 5-tuple:
+
+ * The file name for the module.
+ * A list of line numbers of executable statements.
+ * A list of line numbers of excluded statements.
+ * A list of line numbers of statements not run (missing from
+ execution).
+ * A readable formatted string of the missing line numbers.
+
+ The analysis uses the source file itself and the current measured
+ coverage data.
+
+ """
+ analysis = self._analyze(morf)
+ return (
+ analysis.filename,
+ sorted(analysis.statements),
+ sorted(analysis.excluded),
+ sorted(analysis.missing),
+ analysis.missing_formatted(),
+ )
+
+ def _analyze(self, it):
+ """Analyze a single morf or code unit.
+
+ Returns an `Analysis` object.
+
+ """
+ # All reporting comes through here, so do reporting initialization.
+ self._init()
+ Numbers.set_precision(self.config.precision)
+ self._post_init()
+
+ data = self.get_data()
+ if not isinstance(it, FileReporter):
+ it = self._get_file_reporter(it)
+
+ return Analysis(data, it, self._file_mapper)
+
+ def _get_file_reporter(self, morf):
+ """Get a FileReporter for a module or file name."""
+ plugin = None
+ file_reporter = "python"
+
+ if isinstance(morf, string_class):
+ mapped_morf = self._file_mapper(morf)
+ plugin_name = self._data.file_tracer(mapped_morf)
+ if plugin_name:
+ plugin = self._plugins.get(plugin_name)
+
+ if plugin:
+ file_reporter = plugin.file_reporter(mapped_morf)
+ if file_reporter is None:
+ raise CoverageException(
+ "Plugin %r did not provide a file reporter for %r." % (
+ plugin._coverage_plugin_name, morf
+ )
+ )
+
+ if file_reporter == "python":
+ file_reporter = PythonFileReporter(morf, self)
+
+ return file_reporter
+
+ def _get_file_reporters(self, morfs=None):
+ """Get a list of FileReporters for a list of modules or file names.
+
+ For each module or file name in `morfs`, find a FileReporter. Return
+ the list of FileReporters.
+
+ If `morfs` is a single module or file name, this returns a list of one
+ FileReporter. If `morfs` is empty or None, then the list of all files
+ measured is used to find the FileReporters.
+
+ """
+ if not morfs:
+ morfs = self._data.measured_files()
+
+ # Be sure we have a collection.
+ if not isinstance(morfs, (list, tuple, set)):
+ morfs = [morfs]
+
+ file_reporters = [self._get_file_reporter(morf) for morf in morfs]
+ return file_reporters
+
+ 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,
+ ):
+ """Write a textual summary report to `file`.
+
+ Each module in `morfs` is listed, with counts of statements, executed
+ statements, missing statements, and a list of lines missed.
+
+ If `show_missing` is true, then details of which lines or branches are
+ missing will be included in the report. If `ignore_errors` is true,
+ then a failure while reporting a single file will not stop the entire
+ report.
+
+ `file` is a file-like object, suitable for writing.
+
+ `include` is a list of file name patterns. Files that match will be
+ included in the report. Files matching `omit` will not be included in
+ the report.
+
+ If `skip_covered` is true, don't report on files with 100% coverage.
+
+ If `skip_empty` is true, don't report on empty files (those that have
+ no statements).
+
+ `contexts` is a list of regular expressions. Only data from
+ :ref:`dynamic contexts <dynamic_contexts>` that match one of those
+ expressions (using :func:`re.search <python:re.search>`) will be
+ included in the report.
+
+ All of the arguments default to the settings read from the
+ :ref:`configuration file <config>`.
+
+ Returns a float, the total percentage covered.
+
+ .. versionadded:: 4.0
+ The `skip_covered` parameter.
+
+ .. versionadded:: 5.0
+ The `contexts` and `skip_empty` parameters.
+
+ """
+ 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,
+ ):
+ reporter = SummaryReporter(self)
+ return reporter.report(morfs, outfile=file)
+
+ def annotate(
+ self, morfs=None, directory=None, ignore_errors=None,
+ omit=None, include=None, contexts=None,
+ ):
+ """Annotate a list of modules.
+
+ Each module in `morfs` is annotated. The source is written to a new
+ file, named with a ",cover" suffix, with each line prefixed with a
+ marker to indicate the coverage of the line. Covered lines have ">",
+ excluded lines have "-", and missing lines have "!".
+
+ See :meth:`report` for other arguments.
+
+ """
+ with override_config(self,
+ ignore_errors=ignore_errors, report_omit=omit,
+ report_include=include, report_contexts=contexts,
+ ):
+ 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):
+ """Generate an HTML report.
+
+ The HTML is written to `directory`. The file "index.html" is the
+ overview starting point, with links to more detailed pages for
+ individual modules.
+
+ `extra_css` is a path to a file of other CSS to apply on the page.
+ It will be copied into the HTML directory.
+
+ `title` is a text string (not HTML) to use as the title of the HTML
+ report.
+
+ See :meth:`report` for other arguments.
+
+ Returns a float, the total percentage covered.
+
+ .. note::
+ The HTML report files are generated incrementally based on the
+ source files and coverage results. If you modify the report files,
+ the changes will not be considered. You should be careful about
+ changing the files in the report folder.
+
+ """
+ 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,
+ ):
+ reporter = HtmlReporter(self)
+ return reporter.report(morfs)
+
+ def xml_report(
+ self, morfs=None, outfile=None, ignore_errors=None,
+ omit=None, include=None, contexts=None,
+ ):
+ """Generate an XML report of coverage results.
+
+ The report is compatible with Cobertura reports.
+
+ Each module in `morfs` is included in the report. `outfile` is the
+ path to write the file to, "-" will write to stdout.
+
+ See :meth:`report` for other arguments.
+
+ Returns a float, the total percentage covered.
+
+ """
+ with override_config(self,
+ ignore_errors=ignore_errors, report_omit=omit, report_include=include,
+ xml_output=outfile, report_contexts=contexts,
+ ):
+ return render_report(self.config.xml_output, XmlReporter(self), morfs)
+
+ def json_report(
+ self, morfs=None, outfile=None, ignore_errors=None,
+ omit=None, include=None, contexts=None, pretty_print=None,
+ show_contexts=None
+ ):
+ """Generate a JSON report of coverage results.
+
+ Each module in `morfs` is included in the report. `outfile` is the
+ path to write the file to, "-" will write to stdout.
+
+ See :meth:`report` for other arguments.
+
+ Returns a float, the total percentage covered.
+
+ .. versionadded:: 5.0
+
+ """
+ with override_config(self,
+ ignore_errors=ignore_errors, report_omit=omit, report_include=include,
+ json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
+ json_show_contexts=show_contexts
+ ):
+ return render_report(self.config.json_output, JsonReporter(self), morfs)
+
+ def sys_info(self):
+ """Return a list of (key, value) pairs showing internal information."""
+
+ import coverage as covmod
+
+ self._init()
+ self._post_init()
+
+ def plugin_info(plugins):
+ """Make an entry for the sys_info from a list of plug-ins."""
+ entries = []
+ for plugin in plugins:
+ entry = plugin._coverage_plugin_name
+ if not plugin._coverage_enabled:
+ entry += " (disabled)"
+ entries.append(entry)
+ return entries
+
+ info = [
+ ('version', covmod.__version__),
+ ('coverage', covmod.__file__),
+ ('tracer', self._collector.tracer_name() if self._collector else "-none-"),
+ ('CTracer', 'available' if CTracer else "unavailable"),
+ ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)),
+ ('plugins.configurers', plugin_info(self._plugins.configurers)),
+ ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)),
+ ('configs_attempted', self.config.attempted_config_files),
+ ('configs_read', self.config.config_files_read),
+ ('config_file', self.config.config_file),
+ ('config_contents',
+ repr(self.config._config_contents)
+ if self.config._config_contents
+ else '-none-'
+ ),
+ ('data_file', self._data.data_filename() if self._data is not None else "-none-"),
+ ('python', sys.version.replace('\n', '')),
+ ('platform', platform.platform()),
+ ('implementation', platform.python_implementation()),
+ ('executable', sys.executable),
+ ('def_encoding', sys.getdefaultencoding()),
+ ('fs_encoding', sys.getfilesystemencoding()),
+ ('pid', os.getpid()),
+ ('cwd', os.getcwd()),
+ ('path', sys.path),
+ ('environment', sorted(
+ ("%s = %s" % (k, v))
+ for k, v in iitems(os.environ)
+ if any(slug in k for slug in ("COV", "PY"))
+ )),
+ ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
+ ]
+
+ if self._inorout:
+ info.extend(self._inorout.sys_info())
+
+ info.extend(CoverageData.sys_info())
+
+ return info
+
+
+# Mega debugging...
+# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
+if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging
+ from coverage.debug import decorate_methods, show_calls
+
+ Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage)
+
+
+def process_startup():
+ """Call this at Python start-up to perhaps measure coverage.
+
+ If the environment variable COVERAGE_PROCESS_START is defined, coverage
+ measurement is started. The value of the variable is the config file
+ to use.
+
+ There are two ways to configure your Python installation to invoke this
+ function when Python starts:
+
+ #. Create or append to sitecustomize.py to add these lines::
+
+ import coverage
+ coverage.process_startup()
+
+ #. Create a .pth file in your Python installation containing::
+
+ import coverage; coverage.process_startup()
+
+ Returns the :class:`Coverage` instance that was started, or None if it was
+ not started by this call.
+
+ """
+ cps = os.environ.get("COVERAGE_PROCESS_START")
+ if not cps:
+ # No request for coverage, nothing to do.
+ return None
+
+ # This function can be called more than once in a process. This happens
+ # because some virtualenv configurations make the same directory visible
+ # twice in sys.path. This means that the .pth file will be found twice,
+ # and executed twice, executing this function twice. We set a global
+ # 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.
+
+ if hasattr(process_startup, "coverage"):
+ # We've annotated this function before, so we must have already
+ # started coverage.py in this process. Nothing to do.
+ return None
+
+ cov = Coverage(config_file=cps)
+ process_startup.coverage = cov
+ cov._warn_no_data = False
+ cov._warn_unimported_source = False
+ cov._warn_preimported_source = False
+ cov._auto_save = True
+ cov.start()
+
+ return cov
+
+
+def _prevent_sub_process_measurement():
+ """Stop any subprocess auto-measurement from writing data."""
+ auto_created_coverage = getattr(process_startup, "coverage", None)
+ if auto_created_coverage is not None:
+ auto_created_coverage._auto_save = False
diff --git a/third_party/python/coverage/coverage/ctracer/datastack.c b/third_party/python/coverage/coverage/ctracer/datastack.c
new file mode 100644
index 0000000000..a9cfcc2cf2
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/datastack.c
@@ -0,0 +1,50 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#include "util.h"
+#include "datastack.h"
+
+#define STACK_DELTA 20
+
+int
+DataStack_init(Stats *pstats, DataStack *pdata_stack)
+{
+ pdata_stack->depth = -1;
+ pdata_stack->stack = NULL;
+ pdata_stack->alloc = 0;
+ return RET_OK;
+}
+
+void
+DataStack_dealloc(Stats *pstats, DataStack *pdata_stack)
+{
+ int i;
+
+ for (i = 0; i < pdata_stack->alloc; i++) {
+ Py_XDECREF(pdata_stack->stack[i].file_data);
+ }
+ PyMem_Free(pdata_stack->stack);
+}
+
+int
+DataStack_grow(Stats *pstats, DataStack *pdata_stack)
+{
+ pdata_stack->depth++;
+ if (pdata_stack->depth >= pdata_stack->alloc) {
+ /* We've outgrown our data_stack array: make it bigger. */
+ int bigger = pdata_stack->alloc + STACK_DELTA;
+ DataStackEntry * bigger_data_stack = PyMem_Realloc(pdata_stack->stack, bigger * sizeof(DataStackEntry));
+ STATS( pstats->stack_reallocs++; )
+ if (bigger_data_stack == NULL) {
+ PyErr_NoMemory();
+ pdata_stack->depth--;
+ return RET_ERROR;
+ }
+ /* Zero the new entries. */
+ memset(bigger_data_stack + pdata_stack->alloc, 0, STACK_DELTA * sizeof(DataStackEntry));
+
+ pdata_stack->stack = bigger_data_stack;
+ pdata_stack->alloc = bigger;
+ }
+ return RET_OK;
+}
diff --git a/third_party/python/coverage/coverage/ctracer/datastack.h b/third_party/python/coverage/coverage/ctracer/datastack.h
new file mode 100644
index 0000000000..3b3078ba27
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/datastack.h
@@ -0,0 +1,45 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#ifndef _COVERAGE_DATASTACK_H
+#define _COVERAGE_DATASTACK_H
+
+#include "util.h"
+#include "stats.h"
+
+/* An entry on the data stack. For each call frame, we need to record all
+ * the information needed for CTracer_handle_line to operate as quickly as
+ * possible.
+ */
+typedef struct DataStackEntry {
+ /* The current file_data dictionary. Owned. */
+ PyObject * file_data;
+
+ /* The disposition object for this frame. A borrowed instance of CFileDisposition. */
+ PyObject * disposition;
+
+ /* The FileTracer handling this frame, or None if it's Python. Borrowed. */
+ PyObject * file_tracer;
+
+ /* The line number of the last line recorded, for tracing arcs.
+ -1 means there was no previous line, as when entering a code object.
+ */
+ int last_line;
+
+ BOOL started_context;
+} DataStackEntry;
+
+/* A data stack is a dynamically allocated vector of DataStackEntry's. */
+typedef struct DataStack {
+ int depth; /* The index of the last-used entry in stack. */
+ int alloc; /* number of entries allocated at stack. */
+ /* The file data at each level, or NULL if not recording. */
+ DataStackEntry * stack;
+} DataStack;
+
+
+int DataStack_init(Stats * pstats, DataStack *pdata_stack);
+void DataStack_dealloc(Stats * pstats, DataStack *pdata_stack);
+int DataStack_grow(Stats * pstats, DataStack *pdata_stack);
+
+#endif /* _COVERAGE_DATASTACK_H */
diff --git a/third_party/python/coverage/coverage/ctracer/filedisp.c b/third_party/python/coverage/coverage/ctracer/filedisp.c
new file mode 100644
index 0000000000..47782ae090
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/filedisp.c
@@ -0,0 +1,85 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#include "util.h"
+#include "filedisp.h"
+
+void
+CFileDisposition_dealloc(CFileDisposition *self)
+{
+ Py_XDECREF(self->original_filename);
+ Py_XDECREF(self->canonical_filename);
+ Py_XDECREF(self->source_filename);
+ Py_XDECREF(self->trace);
+ Py_XDECREF(self->reason);
+ Py_XDECREF(self->file_tracer);
+ Py_XDECREF(self->has_dynamic_filename);
+}
+
+static PyMemberDef
+CFileDisposition_members[] = {
+ { "original_filename", T_OBJECT, offsetof(CFileDisposition, original_filename), 0,
+ PyDoc_STR("") },
+
+ { "canonical_filename", T_OBJECT, offsetof(CFileDisposition, canonical_filename), 0,
+ PyDoc_STR("") },
+
+ { "source_filename", T_OBJECT, offsetof(CFileDisposition, source_filename), 0,
+ PyDoc_STR("") },
+
+ { "trace", T_OBJECT, offsetof(CFileDisposition, trace), 0,
+ PyDoc_STR("") },
+
+ { "reason", T_OBJECT, offsetof(CFileDisposition, reason), 0,
+ PyDoc_STR("") },
+
+ { "file_tracer", T_OBJECT, offsetof(CFileDisposition, file_tracer), 0,
+ PyDoc_STR("") },
+
+ { "has_dynamic_filename", T_OBJECT, offsetof(CFileDisposition, has_dynamic_filename), 0,
+ PyDoc_STR("") },
+
+ { NULL }
+};
+
+PyTypeObject
+CFileDispositionType = {
+ MyType_HEAD_INIT
+ "coverage.CFileDispositionType", /*tp_name*/
+ sizeof(CFileDisposition), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)CFileDisposition_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ "CFileDisposition objects", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ CFileDisposition_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
diff --git a/third_party/python/coverage/coverage/ctracer/filedisp.h b/third_party/python/coverage/coverage/ctracer/filedisp.h
new file mode 100644
index 0000000000..860f9a50b1
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/filedisp.h
@@ -0,0 +1,26 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#ifndef _COVERAGE_FILEDISP_H
+#define _COVERAGE_FILEDISP_H
+
+#include "util.h"
+#include "structmember.h"
+
+typedef struct CFileDisposition {
+ PyObject_HEAD
+
+ PyObject * original_filename;
+ PyObject * canonical_filename;
+ PyObject * source_filename;
+ PyObject * trace;
+ PyObject * reason;
+ PyObject * file_tracer;
+ PyObject * has_dynamic_filename;
+} CFileDisposition;
+
+void CFileDisposition_dealloc(CFileDisposition *self);
+
+extern PyTypeObject CFileDispositionType;
+
+#endif /* _COVERAGE_FILEDISP_H */
diff --git a/third_party/python/coverage/coverage/ctracer/module.c b/third_party/python/coverage/coverage/ctracer/module.c
new file mode 100644
index 0000000000..f308902b69
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/module.c
@@ -0,0 +1,108 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#include "util.h"
+#include "tracer.h"
+#include "filedisp.h"
+
+/* Module definition */
+
+#define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
+
+#if PY_MAJOR_VERSION >= 3
+
+static PyModuleDef
+moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "coverage.tracer",
+ MODULE_DOC,
+ -1,
+ NULL, /* methods */
+ NULL,
+ NULL, /* traverse */
+ NULL, /* clear */
+ NULL
+};
+
+
+PyObject *
+PyInit_tracer(void)
+{
+ PyObject * mod = PyModule_Create(&moduledef);
+ if (mod == NULL) {
+ return NULL;
+ }
+
+ if (CTracer_intern_strings() < 0) {
+ return NULL;
+ }
+
+ /* Initialize CTracer */
+ CTracerType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&CTracerType) < 0) {
+ Py_DECREF(mod);
+ return NULL;
+ }
+
+ Py_INCREF(&CTracerType);
+ if (PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType) < 0) {
+ Py_DECREF(mod);
+ Py_DECREF(&CTracerType);
+ return NULL;
+ }
+
+ /* Initialize CFileDisposition */
+ CFileDispositionType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&CFileDispositionType) < 0) {
+ Py_DECREF(mod);
+ Py_DECREF(&CTracerType);
+ return NULL;
+ }
+
+ Py_INCREF(&CFileDispositionType);
+ if (PyModule_AddObject(mod, "CFileDisposition", (PyObject *)&CFileDispositionType) < 0) {
+ Py_DECREF(mod);
+ Py_DECREF(&CTracerType);
+ Py_DECREF(&CFileDispositionType);
+ return NULL;
+ }
+
+ return mod;
+}
+
+#else
+
+void
+inittracer(void)
+{
+ PyObject * mod;
+
+ mod = Py_InitModule3("coverage.tracer", NULL, MODULE_DOC);
+ if (mod == NULL) {
+ return;
+ }
+
+ if (CTracer_intern_strings() < 0) {
+ return;
+ }
+
+ /* Initialize CTracer */
+ CTracerType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&CTracerType) < 0) {
+ return;
+ }
+
+ Py_INCREF(&CTracerType);
+ PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType);
+
+ /* Initialize CFileDisposition */
+ CFileDispositionType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&CFileDispositionType) < 0) {
+ return;
+ }
+
+ Py_INCREF(&CFileDispositionType);
+ PyModule_AddObject(mod, "CFileDisposition", (PyObject *)&CFileDispositionType);
+}
+
+#endif /* Py3k */
diff --git a/third_party/python/coverage/coverage/ctracer/stats.h b/third_party/python/coverage/coverage/ctracer/stats.h
new file mode 100644
index 0000000000..05173369f7
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/stats.h
@@ -0,0 +1,31 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#ifndef _COVERAGE_STATS_H
+#define _COVERAGE_STATS_H
+
+#include "util.h"
+
+#if COLLECT_STATS
+#define STATS(x) x
+#else
+#define STATS(x)
+#endif
+
+typedef struct Stats {
+ unsigned int calls; /* Need at least one member, but the rest only if needed. */
+#if COLLECT_STATS
+ unsigned int lines;
+ unsigned int returns;
+ unsigned int exceptions;
+ unsigned int others;
+ unsigned int files;
+ unsigned int missed_returns;
+ unsigned int stack_reallocs;
+ unsigned int errors;
+ unsigned int pycalls;
+ unsigned int start_context_calls;
+#endif
+} Stats;
+
+#endif /* _COVERAGE_STATS_H */
diff --git a/third_party/python/coverage/coverage/ctracer/tracer.c b/third_party/python/coverage/coverage/ctracer/tracer.c
new file mode 100644
index 0000000000..7d639112db
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/tracer.c
@@ -0,0 +1,1186 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+/* C-based Tracer for coverage.py. */
+
+#include "util.h"
+#include "datastack.h"
+#include "filedisp.h"
+#include "tracer.h"
+
+/* Python C API helpers. */
+
+static int
+pyint_as_int(PyObject * pyint, int *pint)
+{
+ int the_int = MyInt_AsInt(pyint);
+ if (the_int == -1 && PyErr_Occurred()) {
+ return RET_ERROR;
+ }
+
+ *pint = the_int;
+ return RET_OK;
+}
+
+
+/* Interned strings to speed GetAttr etc. */
+
+static PyObject *str_trace;
+static PyObject *str_file_tracer;
+static PyObject *str__coverage_enabled;
+static PyObject *str__coverage_plugin;
+static PyObject *str__coverage_plugin_name;
+static PyObject *str_dynamic_source_filename;
+static PyObject *str_line_number_range;
+
+int
+CTracer_intern_strings(void)
+{
+ int ret = RET_ERROR;
+
+#define INTERN_STRING(v, s) \
+ v = MyText_InternFromString(s); \
+ if (v == NULL) { \
+ goto error; \
+ }
+
+ INTERN_STRING(str_trace, "trace")
+ INTERN_STRING(str_file_tracer, "file_tracer")
+ INTERN_STRING(str__coverage_enabled, "_coverage_enabled")
+ INTERN_STRING(str__coverage_plugin, "_coverage_plugin")
+ INTERN_STRING(str__coverage_plugin_name, "_coverage_plugin_name")
+ INTERN_STRING(str_dynamic_source_filename, "dynamic_source_filename")
+ INTERN_STRING(str_line_number_range, "line_number_range")
+
+ ret = RET_OK;
+
+error:
+ return ret;
+}
+
+static void CTracer_disable_plugin(CTracer *self, PyObject * disposition);
+
+static int
+CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
+{
+ int ret = RET_ERROR;
+
+ if (DataStack_init(&self->stats, &self->data_stack) < 0) {
+ goto error;
+ }
+
+ self->pdata_stack = &self->data_stack;
+
+ self->context = Py_None;
+ Py_INCREF(self->context);
+
+ ret = RET_OK;
+ goto ok;
+
+error:
+ STATS( self->stats.errors++; )
+
+ok:
+ return ret;
+}
+
+static void
+CTracer_dealloc(CTracer *self)
+{
+ int i;
+
+ if (self->started) {
+ PyEval_SetTrace(NULL, NULL);
+ }
+
+ Py_XDECREF(self->should_trace);
+ Py_XDECREF(self->check_include);
+ Py_XDECREF(self->warn);
+ Py_XDECREF(self->concur_id_func);
+ Py_XDECREF(self->data);
+ Py_XDECREF(self->file_tracers);
+ Py_XDECREF(self->should_trace_cache);
+ Py_XDECREF(self->should_start_context);
+ Py_XDECREF(self->switch_context);
+ Py_XDECREF(self->context);
+
+ DataStack_dealloc(&self->stats, &self->data_stack);
+ if (self->data_stacks) {
+ for (i = 0; i < self->data_stacks_used; i++) {
+ DataStack_dealloc(&self->stats, self->data_stacks + i);
+ }
+ PyMem_Free(self->data_stacks);
+ }
+
+ Py_XDECREF(self->data_stack_index);
+
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+#if TRACE_LOG
+static const char *
+indent(int n)
+{
+ static const char * spaces =
+ " "
+ " "
+ " "
+ " "
+ ;
+ return spaces + strlen(spaces) - n*2;
+}
+
+static BOOL logging = FALSE;
+/* Set these constants to be a file substring and line number to start logging. */
+static const char * start_file = "tests/views";
+static int start_line = 27;
+
+static void
+showlog(int depth, int lineno, PyObject * filename, const char * msg)
+{
+ if (logging) {
+ printf("%s%3d ", indent(depth), depth);
+ if (lineno) {
+ printf("%4d", lineno);
+ }
+ else {
+ printf(" ");
+ }
+ if (filename) {
+ PyObject *ascii = MyText_AS_BYTES(filename);
+ printf(" %s", MyBytes_AS_STRING(ascii));
+ Py_DECREF(ascii);
+ }
+ if (msg) {
+ printf(" %s", msg);
+ }
+ printf("\n");
+ }
+}
+
+#define SHOWLOG(a,b,c,d) showlog(a,b,c,d)
+#else
+#define SHOWLOG(a,b,c,d)
+#endif /* TRACE_LOG */
+
+#if WHAT_LOG
+static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
+#endif
+
+/* Record a pair of integers in self->pcur_entry->file_data. */
+static int
+CTracer_record_pair(CTracer *self, int l1, int l2)
+{
+ int ret = RET_ERROR;
+
+ PyObject * t = NULL;
+
+ t = Py_BuildValue("(ii)", l1, l2);
+ if (t == NULL) {
+ goto error;
+ }
+
+ if (PyDict_SetItem(self->pcur_entry->file_data, t, Py_None) < 0) {
+ goto error;
+ }
+
+ ret = RET_OK;
+
+error:
+ Py_XDECREF(t);
+
+ return ret;
+}
+
+/* Set self->pdata_stack to the proper data_stack to use. */
+static int
+CTracer_set_pdata_stack(CTracer *self)
+{
+ int ret = RET_ERROR;
+ PyObject * co_obj = NULL;
+ PyObject * stack_index = NULL;
+
+ if (self->concur_id_func != Py_None) {
+ int the_index = 0;
+
+ if (self->data_stack_index == NULL) {
+ PyObject * weakref = NULL;
+
+ weakref = PyImport_ImportModule("weakref");
+ if (weakref == NULL) {
+ goto error;
+ }
+ STATS( self->stats.pycalls++; )
+ self->data_stack_index = PyObject_CallMethod(weakref, "WeakKeyDictionary", NULL);
+ Py_XDECREF(weakref);
+
+ if (self->data_stack_index == NULL) {
+ goto error;
+ }
+ }
+
+ STATS( self->stats.pycalls++; )
+ co_obj = PyObject_CallObject(self->concur_id_func, NULL);
+ if (co_obj == NULL) {
+ goto error;
+ }
+ stack_index = PyObject_GetItem(self->data_stack_index, co_obj);
+ if (stack_index == NULL) {
+ /* PyObject_GetItem sets an exception if it didn't find the thing. */
+ PyErr_Clear();
+
+ /* A new concurrency object. Make a new data stack. */
+ the_index = self->data_stacks_used;
+ stack_index = MyInt_FromInt(the_index);
+ if (stack_index == NULL) {
+ goto error;
+ }
+ if (PyObject_SetItem(self->data_stack_index, co_obj, stack_index) < 0) {
+ goto error;
+ }
+ self->data_stacks_used++;
+ if (self->data_stacks_used >= self->data_stacks_alloc) {
+ int bigger = self->data_stacks_alloc + 10;
+ DataStack * bigger_stacks = PyMem_Realloc(self->data_stacks, bigger * sizeof(DataStack));
+ if (bigger_stacks == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ self->data_stacks = bigger_stacks;
+ self->data_stacks_alloc = bigger;
+ }
+ DataStack_init(&self->stats, &self->data_stacks[the_index]);
+ }
+ else {
+ if (pyint_as_int(stack_index, &the_index) < 0) {
+ goto error;
+ }
+ }
+
+ self->pdata_stack = &self->data_stacks[the_index];
+ }
+ else {
+ self->pdata_stack = &self->data_stack;
+ }
+
+ ret = RET_OK;
+
+error:
+
+ Py_XDECREF(co_obj);
+ Py_XDECREF(stack_index);
+
+ return ret;
+}
+
+/*
+ * Parts of the trace function.
+ */
+
+static int
+CTracer_check_missing_return(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+
+ if (self->last_exc_back) {
+ if (frame == self->last_exc_back) {
+ /* Looks like someone forgot to send a return event. We'll clear
+ the exception state and do the RETURN code here. Notice that the
+ frame we have in hand here is not the correct frame for the RETURN,
+ that frame is gone. Our handling for RETURN doesn't need the
+ actual frame, but we do log it, so that will look a little off if
+ you're looking at the detailed log.
+
+ If someday we need to examine the frame when doing RETURN, then
+ we'll need to keep more of the missed frame's state.
+ */
+ STATS( self->stats.missed_returns++; )
+ if (CTracer_set_pdata_stack(self) < 0) {
+ goto error;
+ }
+ if (self->pdata_stack->depth >= 0) {
+ if (self->tracing_arcs && self->pcur_entry->file_data) {
+ if (CTracer_record_pair(self, self->pcur_entry->last_line, -self->last_exc_firstlineno) < 0) {
+ goto error;
+ }
+ }
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn");
+ self->pdata_stack->depth--;
+ self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
+ }
+ }
+ self->last_exc_back = NULL;
+ }
+
+ ret = RET_OK;
+
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_call(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+ int ret2;
+
+ /* Owned references that we clean up at the very end of the function. */
+ PyObject * disposition = NULL;
+ PyObject * plugin = NULL;
+ PyObject * plugin_name = NULL;
+ PyObject * next_tracename = NULL;
+
+ /* Borrowed references. */
+ PyObject * filename = NULL;
+ PyObject * disp_trace = NULL;
+ PyObject * tracename = NULL;
+ PyObject * file_tracer = NULL;
+ PyObject * has_dynamic_filename = NULL;
+
+ CFileDisposition * pdisp = NULL;
+
+ STATS( self->stats.calls++; )
+
+ /* Grow the stack. */
+ if (CTracer_set_pdata_stack(self) < 0) {
+ goto error;
+ }
+ if (DataStack_grow(&self->stats, self->pdata_stack) < 0) {
+ goto error;
+ }
+ self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
+
+ /* See if this frame begins a new context. */
+ if (self->should_start_context != Py_None && self->context == Py_None) {
+ PyObject * context;
+ /* We're looking for our context, ask should_start_context if this is the start. */
+ STATS( self->stats.start_context_calls++; )
+ STATS( self->stats.pycalls++; )
+ context = PyObject_CallFunctionObjArgs(self->should_start_context, frame, NULL);
+ if (context == NULL) {
+ goto error;
+ }
+ if (context != Py_None) {
+ PyObject * val;
+ Py_DECREF(self->context);
+ self->context = context;
+ self->pcur_entry->started_context = TRUE;
+ STATS( self->stats.pycalls++; )
+ val = PyObject_CallFunctionObjArgs(self->switch_context, context, NULL);
+ if (val == NULL) {
+ goto error;
+ }
+ Py_DECREF(val);
+ }
+ else {
+ Py_DECREF(context);
+ self->pcur_entry->started_context = FALSE;
+ }
+ }
+ else {
+ self->pcur_entry->started_context = FALSE;
+ }
+
+ /* Check if we should trace this line. */
+ filename = frame->f_code->co_filename;
+ disposition = PyDict_GetItem(self->should_trace_cache, filename);
+ if (disposition == NULL) {
+ if (PyErr_Occurred()) {
+ goto error;
+ }
+ STATS( self->stats.files++; )
+
+ /* We've never considered this file before. */
+ /* Ask should_trace about it. */
+ STATS( self->stats.pycalls++; )
+ disposition = PyObject_CallFunctionObjArgs(self->should_trace, filename, frame, NULL);
+ if (disposition == NULL) {
+ /* An error occurred inside should_trace. */
+ goto error;
+ }
+ if (PyDict_SetItem(self->should_trace_cache, filename, disposition) < 0) {
+ goto error;
+ }
+ }
+ else {
+ Py_INCREF(disposition);
+ }
+
+ if (disposition == Py_None) {
+ /* A later check_include returned false, so don't trace it. */
+ disp_trace = Py_False;
+ }
+ else {
+ /* The object we got is a CFileDisposition, use it efficiently. */
+ pdisp = (CFileDisposition *) disposition;
+ disp_trace = pdisp->trace;
+ if (disp_trace == NULL) {
+ goto error;
+ }
+ }
+
+ if (disp_trace == Py_True) {
+ /* If tracename is a string, then we're supposed to trace. */
+ tracename = pdisp->source_filename;
+ if (tracename == NULL) {
+ goto error;
+ }
+ file_tracer = pdisp->file_tracer;
+ if (file_tracer == NULL) {
+ goto error;
+ }
+ if (file_tracer != Py_None) {
+ plugin = PyObject_GetAttr(file_tracer, str__coverage_plugin);
+ if (plugin == NULL) {
+ goto error;
+ }
+ plugin_name = PyObject_GetAttr(plugin, str__coverage_plugin_name);
+ if (plugin_name == NULL) {
+ goto error;
+ }
+ }
+ has_dynamic_filename = pdisp->has_dynamic_filename;
+ if (has_dynamic_filename == NULL) {
+ goto error;
+ }
+ if (has_dynamic_filename == Py_True) {
+ STATS( self->stats.pycalls++; )
+ next_tracename = PyObject_CallMethodObjArgs(
+ file_tracer, str_dynamic_source_filename,
+ tracename, frame, NULL
+ );
+ if (next_tracename == NULL) {
+ /* An exception from the function. Alert the user with a
+ * warning and a traceback.
+ */
+ CTracer_disable_plugin(self, disposition);
+ /* Because we handled the error, goto ok. */
+ goto ok;
+ }
+ tracename = next_tracename;
+
+ if (tracename != Py_None) {
+ /* Check the dynamic source filename against the include rules. */
+ PyObject * included = NULL;
+ int should_include;
+ included = PyDict_GetItem(self->should_trace_cache, tracename);
+ if (included == NULL) {
+ PyObject * should_include_bool;
+ if (PyErr_Occurred()) {
+ goto error;
+ }
+ STATS( self->stats.files++; )
+ STATS( self->stats.pycalls++; )
+ should_include_bool = PyObject_CallFunctionObjArgs(self->check_include, tracename, frame, NULL);
+ if (should_include_bool == NULL) {
+ goto error;
+ }
+ should_include = (should_include_bool == Py_True);
+ Py_DECREF(should_include_bool);
+ if (PyDict_SetItem(self->should_trace_cache, tracename, should_include ? disposition : Py_None) < 0) {
+ goto error;
+ }
+ }
+ else {
+ should_include = (included != Py_None);
+ }
+ if (!should_include) {
+ tracename = Py_None;
+ }
+ }
+ }
+ }
+ else {
+ tracename = Py_None;
+ }
+
+ if (tracename != Py_None) {
+ PyObject * file_data = PyDict_GetItem(self->data, tracename);
+
+ if (file_data == NULL) {
+ if (PyErr_Occurred()) {
+ goto error;
+ }
+ file_data = PyDict_New();
+ if (file_data == NULL) {
+ goto error;
+ }
+ ret2 = PyDict_SetItem(self->data, tracename, file_data);
+ if (ret2 < 0) {
+ goto error;
+ }
+
+ /* If the disposition mentions a plugin, record that. */
+ if (file_tracer != Py_None) {
+ ret2 = PyDict_SetItem(self->file_tracers, tracename, plugin_name);
+ if (ret2 < 0) {
+ goto error;
+ }
+ }
+ }
+ else {
+ /* PyDict_GetItem gives a borrowed reference. Own it. */
+ Py_INCREF(file_data);
+ }
+
+ Py_XDECREF(self->pcur_entry->file_data);
+ self->pcur_entry->file_data = file_data;
+ self->pcur_entry->file_tracer = file_tracer;
+
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "traced");
+ }
+ else {
+ Py_XDECREF(self->pcur_entry->file_data);
+ self->pcur_entry->file_data = NULL;
+ self->pcur_entry->file_tracer = Py_None;
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, filename, "skipped");
+ }
+
+ self->pcur_entry->disposition = disposition;
+
+ /* Make the frame right in case settrace(gettrace()) happens. */
+ Py_INCREF(self);
+ My_XSETREF(frame->f_trace, (PyObject*)self);
+
+ /* A call event is really a "start frame" event, and can happen for
+ * re-entering a generator also. f_lasti is -1 for a true call, and a
+ * real byte offset for a generator re-entry.
+ */
+ if (frame->f_lasti < 0) {
+ self->pcur_entry->last_line = -frame->f_code->co_firstlineno;
+ }
+ else {
+ self->pcur_entry->last_line = frame->f_lineno;
+ }
+
+ok:
+ ret = RET_OK;
+
+error:
+ Py_XDECREF(next_tracename);
+ Py_XDECREF(disposition);
+ Py_XDECREF(plugin);
+ Py_XDECREF(plugin_name);
+
+ return ret;
+}
+
+
+static void
+CTracer_disable_plugin(CTracer *self, PyObject * disposition)
+{
+ PyObject * file_tracer = NULL;
+ PyObject * plugin = NULL;
+ PyObject * plugin_name = NULL;
+ PyObject * msg = NULL;
+ PyObject * ignored = NULL;
+
+ PyErr_Print();
+
+ file_tracer = PyObject_GetAttr(disposition, str_file_tracer);
+ if (file_tracer == NULL) {
+ goto error;
+ }
+ if (file_tracer == Py_None) {
+ /* This shouldn't happen... */
+ goto ok;
+ }
+ plugin = PyObject_GetAttr(file_tracer, str__coverage_plugin);
+ if (plugin == NULL) {
+ goto error;
+ }
+ plugin_name = PyObject_GetAttr(plugin, str__coverage_plugin_name);
+ if (plugin_name == NULL) {
+ goto error;
+ }
+ msg = MyText_FromFormat(
+ "Disabling plug-in '%s' due to previous exception",
+ MyText_AsString(plugin_name)
+ );
+ if (msg == NULL) {
+ goto error;
+ }
+ STATS( self->stats.pycalls++; )
+ ignored = PyObject_CallFunctionObjArgs(self->warn, msg, NULL);
+ if (ignored == NULL) {
+ goto error;
+ }
+
+ /* Disable the plugin for future files, and stop tracing this file. */
+ if (PyObject_SetAttr(plugin, str__coverage_enabled, Py_False) < 0) {
+ goto error;
+ }
+ if (PyObject_SetAttr(disposition, str_trace, Py_False) < 0) {
+ goto error;
+ }
+
+ goto ok;
+
+error:
+ /* This function doesn't return a status, so if an error happens, print it,
+ * but don't interrupt the flow. */
+ /* PySys_WriteStderr is nicer, but is not in the public API. */
+ fprintf(stderr, "Error occurred while disabling plug-in:\n");
+ PyErr_Print();
+
+ok:
+ Py_XDECREF(file_tracer);
+ Py_XDECREF(plugin);
+ Py_XDECREF(plugin_name);
+ Py_XDECREF(msg);
+ Py_XDECREF(ignored);
+}
+
+
+static int
+CTracer_unpack_pair(CTracer *self, PyObject *pair, int *p_one, int *p_two)
+{
+ int ret = RET_ERROR;
+ int the_int;
+ PyObject * pyint = NULL;
+ int index;
+
+ if (!PyTuple_Check(pair) || PyTuple_Size(pair) != 2) {
+ PyErr_SetString(
+ PyExc_TypeError,
+ "line_number_range must return 2-tuple"
+ );
+ goto error;
+ }
+
+ for (index = 0; index < 2; index++) {
+ pyint = PyTuple_GetItem(pair, index);
+ if (pyint == NULL) {
+ goto error;
+ }
+ if (pyint_as_int(pyint, &the_int) < 0) {
+ goto error;
+ }
+ *(index == 0 ? p_one : p_two) = the_int;
+ }
+
+ ret = RET_OK;
+
+error:
+ return ret;
+}
+
+static int
+CTracer_handle_line(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+ int ret2;
+
+ STATS( self->stats.lines++; )
+ if (self->pdata_stack->depth >= 0) {
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "line");
+ if (self->pcur_entry->file_data) {
+ int lineno_from = -1;
+ int lineno_to = -1;
+
+ /* We're tracing in this frame: record something. */
+ if (self->pcur_entry->file_tracer != Py_None) {
+ PyObject * from_to = NULL;
+ STATS( self->stats.pycalls++; )
+ from_to = PyObject_CallMethodObjArgs(self->pcur_entry->file_tracer, str_line_number_range, frame, NULL);
+ if (from_to == NULL) {
+ goto error;
+ }
+ ret2 = CTracer_unpack_pair(self, from_to, &lineno_from, &lineno_to);
+ Py_DECREF(from_to);
+ if (ret2 < 0) {
+ CTracer_disable_plugin(self, self->pcur_entry->disposition);
+ goto ok;
+ }
+ }
+ else {
+ lineno_from = lineno_to = frame->f_lineno;
+ }
+
+ if (lineno_from != -1) {
+ for (; lineno_from <= lineno_to; lineno_from++) {
+ if (self->tracing_arcs) {
+ /* Tracing arcs: key is (last_line,this_line). */
+ if (CTracer_record_pair(self, self->pcur_entry->last_line, lineno_from) < 0) {
+ goto error;
+ }
+ }
+ else {
+ /* Tracing lines: key is simply this_line. */
+ PyObject * this_line = MyInt_FromInt(lineno_from);
+ if (this_line == NULL) {
+ goto error;
+ }
+
+ ret2 = PyDict_SetItem(self->pcur_entry->file_data, this_line, Py_None);
+ Py_DECREF(this_line);
+ if (ret2 < 0) {
+ goto error;
+ }
+ }
+
+ self->pcur_entry->last_line = lineno_from;
+ }
+ }
+ }
+ }
+
+ok:
+ ret = RET_OK;
+
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_return(CTracer *self, PyFrameObject *frame)
+{
+ int ret = RET_ERROR;
+
+ STATS( self->stats.returns++; )
+ /* A near-copy of this code is above in the missing-return handler. */
+ if (CTracer_set_pdata_stack(self) < 0) {
+ goto error;
+ }
+ self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
+
+ if (self->pdata_stack->depth >= 0) {
+ if (self->tracing_arcs && self->pcur_entry->file_data) {
+ /* Need to distinguish between RETURN_VALUE and YIELD_VALUE. Read
+ * the current bytecode to see what it is. In unusual circumstances
+ * (Cython code), co_code can be the empty string, so range-check
+ * f_lasti before reading the byte.
+ */
+ int bytecode = RETURN_VALUE;
+ PyObject * pCode = frame->f_code->co_code;
+ int lasti = frame->f_lasti;
+
+ if (lasti < MyBytes_GET_SIZE(pCode)) {
+ bytecode = MyBytes_AS_STRING(pCode)[lasti];
+ }
+ if (bytecode != YIELD_VALUE) {
+ int first = frame->f_code->co_firstlineno;
+ if (CTracer_record_pair(self, self->pcur_entry->last_line, -first) < 0) {
+ goto error;
+ }
+ }
+ }
+
+ /* If this frame started a context, then returning from it ends the context. */
+ if (self->pcur_entry->started_context) {
+ PyObject * val;
+ Py_DECREF(self->context);
+ self->context = Py_None;
+ Py_INCREF(self->context);
+ STATS( self->stats.pycalls++; )
+
+ val = PyObject_CallFunctionObjArgs(self->switch_context, self->context, NULL);
+ if (val == NULL) {
+ goto error;
+ }
+ Py_DECREF(val);
+ }
+
+ /* Pop the stack. */
+ SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "return");
+ self->pdata_stack->depth--;
+ self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
+ }
+
+ ret = RET_OK;
+
+error:
+
+ return ret;
+}
+
+static int
+CTracer_handle_exception(CTracer *self, PyFrameObject *frame)
+{
+ /* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
+ without a return event. To detect that, we'll keep a copy of the
+ parent frame for an exception event. If the next event is in that
+ frame, then we must have returned without a return event. We can
+ synthesize the missing event then.
+
+ Python itself fixed this problem in 2.4. Pyexpat still has the bug.
+ I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
+ If it gets fixed, this code should still work properly. Maybe some day
+ the bug will be fixed everywhere coverage.py is supported, and we can
+ remove this missing-return detection.
+
+ More about this fix: https://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
+ */
+ STATS( self->stats.exceptions++; )
+ self->last_exc_back = frame->f_back;
+ self->last_exc_firstlineno = frame->f_code->co_firstlineno;
+
+ return RET_OK;
+}
+
+/*
+ * The Trace Function
+ */
+static int
+CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
+{
+ int ret = RET_ERROR;
+
+ #if DO_NOTHING
+ return RET_OK;
+ #endif
+
+ if (!self->started) {
+ /* If CTracer.stop() has been called from another thread, the tracer
+ is still active in the current thread. Let's deactivate ourselves
+ now. */
+ PyEval_SetTrace(NULL, NULL);
+ return RET_OK;
+ }
+
+ #if WHAT_LOG || TRACE_LOG
+ PyObject * ascii = NULL;
+ #endif
+
+ #if WHAT_LOG
+ if (what <= (int)(sizeof(what_sym)/sizeof(const char *))) {
+ ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+ printf("trace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), frame->f_lineno);
+ Py_DECREF(ascii);
+ }
+ #endif
+
+ #if TRACE_LOG
+ ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+ if (strstr(MyBytes_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) {
+ logging = TRUE;
+ }
+ Py_DECREF(ascii);
+ #endif
+
+ /* See below for details on missing-return detection. */
+ if (CTracer_check_missing_return(self, frame) < 0) {
+ goto error;
+ }
+
+ self->activity = TRUE;
+
+ switch (what) {
+ case PyTrace_CALL:
+ if (CTracer_handle_call(self, frame) < 0) {
+ goto error;
+ }
+ break;
+
+ case PyTrace_RETURN:
+ if (CTracer_handle_return(self, frame) < 0) {
+ goto error;
+ }
+ break;
+
+ case PyTrace_LINE:
+ if (CTracer_handle_line(self, frame) < 0) {
+ goto error;
+ }
+ break;
+
+ case PyTrace_EXCEPTION:
+ if (CTracer_handle_exception(self, frame) < 0) {
+ goto error;
+ }
+ break;
+
+ default:
+ STATS( self->stats.others++; )
+ break;
+ }
+
+ ret = RET_OK;
+ goto cleanup;
+
+error:
+ STATS( self->stats.errors++; )
+
+cleanup:
+ return ret;
+}
+
+
+/*
+ * Python has two ways to set the trace function: sys.settrace(fn), which
+ * takes a Python callable, and PyEval_SetTrace(func, obj), which takes
+ * a C function and a Python object. The way these work together is that
+ * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
+ * Python callable as the object in PyEval_SetTrace. So sys.gettrace()
+ * simply returns the Python object used as the second argument to
+ * PyEval_SetTrace. So sys.gettrace() will return our self parameter, which
+ * means it must be callable to be used in sys.settrace().
+ *
+ * So we make ourself callable, equivalent to invoking our trace function.
+ *
+ * To help with the process of replaying stored frames, this function has an
+ * optional keyword argument:
+ *
+ * def CTracer_call(frame, event, arg, lineno=0)
+ *
+ * If provided, the lineno argument is used as the line number, and the
+ * frame's f_lineno member is ignored.
+ */
+static PyObject *
+CTracer_call(CTracer *self, PyObject *args, PyObject *kwds)
+{
+ PyFrameObject *frame;
+ PyObject *what_str;
+ PyObject *arg;
+ int lineno = 0;
+ int what;
+ int orig_lineno;
+ PyObject *ret = NULL;
+ PyObject * ascii = NULL;
+
+ #if DO_NOTHING
+ CRASH
+ #endif
+
+ static char *what_names[] = {
+ "call", "exception", "line", "return",
+ "c_call", "c_exception", "c_return",
+ NULL
+ };
+
+ static char *kwlist[] = {"frame", "event", "arg", "lineno", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!O|i:Tracer_call", kwlist,
+ &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg, &lineno)) {
+ goto done;
+ }
+
+ /* In Python, the what argument is a string, we need to find an int
+ for the C function. */
+ for (what = 0; what_names[what]; what++) {
+ int should_break;
+ ascii = MyText_AS_BYTES(what_str);
+ should_break = !strcmp(MyBytes_AS_STRING(ascii), what_names[what]);
+ Py_DECREF(ascii);
+ if (should_break) {
+ break;
+ }
+ }
+
+ #if WHAT_LOG
+ ascii = MyText_AS_BYTES(frame->f_code->co_filename);
+ printf("pytrace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), frame->f_lineno);
+ Py_DECREF(ascii);
+ #endif
+
+ /* Save off the frame's lineno, and use the forced one, if provided. */
+ orig_lineno = frame->f_lineno;
+ if (lineno > 0) {
+ frame->f_lineno = lineno;
+ }
+
+ /* Invoke the C function, and return ourselves. */
+ if (CTracer_trace(self, frame, what, arg) == RET_OK) {
+ Py_INCREF(self);
+ ret = (PyObject *)self;
+ }
+
+ /* Clean up. */
+ frame->f_lineno = orig_lineno;
+
+ /* For better speed, install ourselves the C way so that future calls go
+ directly to CTracer_trace, without this intermediate function.
+
+ Only do this if this is a CALL event, since new trace functions only
+ take effect then. If we don't condition it on CALL, then we'll clobber
+ the new trace function before it has a chance to get called. To
+ understand why, there are three internal values to track: frame.f_trace,
+ c_tracefunc, and c_traceobj. They are explained here:
+ https://nedbatchelder.com/text/trace-function.html
+
+ Without the conditional on PyTrace_CALL, this is what happens:
+
+ def func(): # f_trace c_tracefunc c_traceobj
+ # -------------- -------------- --------------
+ # CTracer CTracer.trace CTracer
+ sys.settrace(my_func)
+ # CTracer trampoline my_func
+ # Now Python calls trampoline(CTracer), which calls this function
+ # which calls PyEval_SetTrace below, setting us as the tracer again:
+ # CTracer CTracer.trace CTracer
+ # and it's as if the settrace never happened.
+ */
+ if (what == PyTrace_CALL) {
+ PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
+ }
+
+done:
+ return ret;
+}
+
+static PyObject *
+CTracer_start(CTracer *self, PyObject *args_unused)
+{
+ PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self);
+ self->started = TRUE;
+ self->tracing_arcs = self->trace_arcs && PyObject_IsTrue(self->trace_arcs);
+
+ /* start() returns a trace function usable with sys.settrace() */
+ Py_INCREF(self);
+ return (PyObject *)self;
+}
+
+static PyObject *
+CTracer_stop(CTracer *self, PyObject *args_unused)
+{
+ if (self->started) {
+ /* Set the started flag only. The actual call to
+ PyEval_SetTrace(NULL, NULL) is delegated to the callback
+ itself to ensure that it called from the right thread.
+ */
+ self->started = FALSE;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+CTracer_activity(CTracer *self, PyObject *args_unused)
+{
+ if (self->activity) {
+ Py_RETURN_TRUE;
+ }
+ else {
+ Py_RETURN_FALSE;
+ }
+}
+
+static PyObject *
+CTracer_reset_activity(CTracer *self, PyObject *args_unused)
+{
+ self->activity = FALSE;
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+CTracer_get_stats(CTracer *self, PyObject *args_unused)
+{
+#if COLLECT_STATS
+ return Py_BuildValue(
+ "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI,sI,sI}",
+ "calls", self->stats.calls,
+ "lines", self->stats.lines,
+ "returns", self->stats.returns,
+ "exceptions", self->stats.exceptions,
+ "others", self->stats.others,
+ "files", self->stats.files,
+ "missed_returns", self->stats.missed_returns,
+ "stack_reallocs", self->stats.stack_reallocs,
+ "stack_alloc", self->pdata_stack->alloc,
+ "errors", self->stats.errors,
+ "pycalls", self->stats.pycalls,
+ "start_context_calls", self->stats.start_context_calls
+ );
+#else
+ Py_RETURN_NONE;
+#endif /* COLLECT_STATS */
+}
+
+static PyMemberDef
+CTracer_members[] = {
+ { "should_trace", T_OBJECT, offsetof(CTracer, should_trace), 0,
+ PyDoc_STR("Function indicating whether to trace a file.") },
+
+ { "check_include", T_OBJECT, offsetof(CTracer, check_include), 0,
+ PyDoc_STR("Function indicating whether to include a file.") },
+
+ { "warn", T_OBJECT, offsetof(CTracer, warn), 0,
+ PyDoc_STR("Function for issuing warnings.") },
+
+ { "concur_id_func", T_OBJECT, offsetof(CTracer, concur_id_func), 0,
+ PyDoc_STR("Function for determining concurrency context") },
+
+ { "data", T_OBJECT, offsetof(CTracer, data), 0,
+ PyDoc_STR("The raw dictionary of trace data.") },
+
+ { "file_tracers", T_OBJECT, offsetof(CTracer, file_tracers), 0,
+ PyDoc_STR("Mapping from file name to plugin name.") },
+
+ { "should_trace_cache", T_OBJECT, offsetof(CTracer, should_trace_cache), 0,
+ PyDoc_STR("Dictionary caching should_trace results.") },
+
+ { "trace_arcs", T_OBJECT, offsetof(CTracer, trace_arcs), 0,
+ PyDoc_STR("Should we trace arcs, or just lines?") },
+
+ { "should_start_context", T_OBJECT, offsetof(CTracer, should_start_context), 0,
+ PyDoc_STR("Function for starting contexts.") },
+
+ { "switch_context", T_OBJECT, offsetof(CTracer, switch_context), 0,
+ PyDoc_STR("Function for switching to a new context.") },
+
+ { NULL }
+};
+
+static PyMethodDef
+CTracer_methods[] = {
+ { "start", (PyCFunction) CTracer_start, METH_VARARGS,
+ PyDoc_STR("Start the tracer") },
+
+ { "stop", (PyCFunction) CTracer_stop, METH_VARARGS,
+ PyDoc_STR("Stop the tracer") },
+
+ { "get_stats", (PyCFunction) CTracer_get_stats, METH_VARARGS,
+ PyDoc_STR("Get statistics about the tracing") },
+
+ { "activity", (PyCFunction) CTracer_activity, METH_VARARGS,
+ PyDoc_STR("Has there been any activity?") },
+
+ { "reset_activity", (PyCFunction) CTracer_reset_activity, METH_VARARGS,
+ PyDoc_STR("Reset the activity flag") },
+
+ { NULL }
+};
+
+PyTypeObject
+CTracerType = {
+ MyType_HEAD_INIT
+ "coverage.CTracer", /*tp_name*/
+ sizeof(CTracer), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)CTracer_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ (ternaryfunc)CTracer_call, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ "CTracer objects", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ CTracer_methods, /* tp_methods */
+ CTracer_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)CTracer_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
diff --git a/third_party/python/coverage/coverage/ctracer/tracer.h b/third_party/python/coverage/coverage/ctracer/tracer.h
new file mode 100644
index 0000000000..a83742ddf3
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/tracer.h
@@ -0,0 +1,74 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#ifndef _COVERAGE_TRACER_H
+#define _COVERAGE_TRACER_H
+
+#include "util.h"
+#include "structmember.h"
+#include "frameobject.h"
+#include "opcode.h"
+
+#include "datastack.h"
+
+/* The CTracer type. */
+
+typedef struct CTracer {
+ PyObject_HEAD
+
+ /* Python objects manipulated directly by the Collector class. */
+ PyObject * should_trace;
+ PyObject * check_include;
+ PyObject * warn;
+ PyObject * concur_id_func;
+ PyObject * data;
+ PyObject * file_tracers;
+ PyObject * should_trace_cache;
+ PyObject * trace_arcs;
+ PyObject * should_start_context;
+ PyObject * switch_context;
+
+ /* Has the tracer been started? */
+ BOOL started;
+ /* Are we tracing arcs, or just lines? */
+ BOOL tracing_arcs;
+ /* Have we had any activity? */
+ BOOL activity;
+ /* The current dynamic context. */
+ PyObject * context;
+
+ /*
+ The data stack is a stack of dictionaries. Each dictionary collects
+ data for a single source file. The data stack parallels the call stack:
+ each call pushes the new frame's file data onto the data stack, and each
+ return pops file data off.
+
+ The file data is a dictionary whose form depends on the tracing options.
+ If tracing arcs, the keys are line number pairs. If not tracing arcs,
+ the keys are line numbers. In both cases, the value is irrelevant
+ (None).
+ */
+
+ DataStack data_stack; /* Used if we aren't doing concurrency. */
+
+ PyObject * data_stack_index; /* Used if we are doing concurrency. */
+ DataStack * data_stacks;
+ int data_stacks_alloc;
+ int data_stacks_used;
+ DataStack * pdata_stack;
+
+ /* The current file's data stack entry. */
+ DataStackEntry * pcur_entry;
+
+ /* The parent frame for the last exception event, to fix missing returns. */
+ PyFrameObject * last_exc_back;
+ int last_exc_firstlineno;
+
+ Stats stats;
+} CTracer;
+
+int CTracer_intern_strings(void);
+
+extern PyTypeObject CTracerType;
+
+#endif /* _COVERAGE_TRACER_H */
diff --git a/third_party/python/coverage/coverage/ctracer/util.h b/third_party/python/coverage/coverage/ctracer/util.h
new file mode 100644
index 0000000000..5cba9b3096
--- /dev/null
+++ b/third_party/python/coverage/coverage/ctracer/util.h
@@ -0,0 +1,67 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+#ifndef _COVERAGE_UTIL_H
+#define _COVERAGE_UTIL_H
+
+#include <Python.h>
+
+/* Compile-time debugging helpers */
+#undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
+#undef TRACE_LOG /* Define to log our bookkeeping. */
+#undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */
+#undef DO_NOTHING /* Define this to make the tracer do nothing. */
+
+/* Py 2.x and 3.x compatibility */
+
+#if PY_MAJOR_VERSION >= 3
+
+#define MyText_Type PyUnicode_Type
+#define MyText_AS_BYTES(o) PyUnicode_AsASCIIString(o)
+#define MyBytes_GET_SIZE(o) PyBytes_GET_SIZE(o)
+#define MyBytes_AS_STRING(o) PyBytes_AS_STRING(o)
+#define MyText_AsString(o) PyUnicode_AsUTF8(o)
+#define MyText_FromFormat PyUnicode_FromFormat
+#define MyInt_FromInt(i) PyLong_FromLong((long)i)
+#define MyInt_AsInt(o) (int)PyLong_AsLong(o)
+#define MyText_InternFromString(s) PyUnicode_InternFromString(s)
+
+#define MyType_HEAD_INIT PyVarObject_HEAD_INIT(NULL, 0)
+
+#else
+
+#define MyText_Type PyString_Type
+#define MyText_AS_BYTES(o) (Py_INCREF(o), o)
+#define MyBytes_GET_SIZE(o) PyString_GET_SIZE(o)
+#define MyBytes_AS_STRING(o) PyString_AS_STRING(o)
+#define MyText_AsString(o) PyString_AsString(o)
+#define MyText_FromFormat PyUnicode_FromFormat
+#define MyInt_FromInt(i) PyInt_FromLong((long)i)
+#define MyInt_AsInt(o) (int)PyInt_AsLong(o)
+#define MyText_InternFromString(s) PyString_InternFromString(s)
+
+#define MyType_HEAD_INIT PyObject_HEAD_INIT(NULL) 0,
+
+#endif /* Py3k */
+
+// Undocumented, and not in all 2.7.x, so our own copy of it.
+#define My_XSETREF(op, op2) \
+ do { \
+ PyObject *_py_tmp = (PyObject *)(op); \
+ (op) = (op2); \
+ Py_XDECREF(_py_tmp); \
+ } while (0)
+
+/* The values returned to indicate ok or error. */
+#define RET_OK 0
+#define RET_ERROR -1
+
+/* Nicer booleans */
+typedef int BOOL;
+#define FALSE 0
+#define TRUE 1
+
+/* Only for extreme machete-mode debugging! */
+#define CRASH { printf("*** CRASH! ***\n"); *((int*)1) = 1; }
+
+#endif /* _COVERAGE_UTIL_H */
diff --git a/third_party/python/coverage/coverage/data.py b/third_party/python/coverage/coverage/data.py
new file mode 100644
index 0000000000..82bf1d41c1
--- /dev/null
+++ b/third_party/python/coverage/coverage/data.py
@@ -0,0 +1,124 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Coverage data for coverage.py.
+
+This file had the 4.x JSON data support, which is now gone. This file still
+has storage-agnostic helpers, and is kept to avoid changing too many imports.
+CoverageData is now defined in sqldata.py, and imported here to keep the
+imports working.
+
+"""
+
+import glob
+import os.path
+
+from coverage.misc import CoverageException, file_be_gone
+from coverage.sqldata import CoverageData
+
+
+def line_counts(data, fullpath=False):
+ """Return a dict summarizing the line coverage data.
+
+ Keys are based on the file names, and values are the number of executed
+ lines. If `fullpath` is true, then the keys are the full pathnames of
+ the files, otherwise they are the basenames of the files.
+
+ Returns a dict mapping file names to counts of lines.
+
+ """
+ summ = {}
+ if fullpath:
+ filename_fn = lambda f: f
+ else:
+ filename_fn = os.path.basename
+ for filename in data.measured_files():
+ summ[filename_fn(filename)] = len(data.lines(filename))
+ return summ
+
+
+def add_data_to_hash(data, filename, hasher):
+ """Contribute `filename`'s data to the `hasher`.
+
+ `hasher` is a `coverage.misc.Hasher` instance to be updated with
+ the file's data. It should only get the results data, not the run
+ data.
+
+ """
+ if data.has_arcs():
+ hasher.update(sorted(data.arcs(filename) or []))
+ else:
+ hasher.update(sorted(data.lines(filename) or []))
+ hasher.update(data.file_tracer(filename))
+
+
+def combine_parallel_data(data, aliases=None, data_paths=None, strict=False):
+ """Combine a number of data files together.
+
+ Treat `data.filename` as a file prefix, and combine the data from all
+ of the data files starting with that prefix plus a dot.
+
+ If `aliases` is provided, it's a `PathAliases` object that is used to
+ re-map paths to match the local machine's.
+
+ If `data_paths` is provided, it is a list of directories or files to
+ combine. Directories are searched for files that start with
+ `data.filename` plus dot as a prefix, and those files are combined.
+
+ 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
+ cannot be read, a warning will be issued, and the file will not be
+ deleted.
+
+ If `strict` is true, and no files are found to combine, an error is
+ raised.
+
+ """
+ # Because of the os.path.abspath in the constructor, data_dir will
+ # never be an empty string.
+ data_dir, local = os.path.split(data.base_filename())
+ localdot = local + '.*'
+
+ data_paths = data_paths or [data_dir]
+ files_to_combine = []
+ for p in data_paths:
+ if os.path.isfile(p):
+ files_to_combine.append(os.path.abspath(p))
+ elif os.path.isdir(p):
+ pattern = os.path.join(os.path.abspath(p), localdot)
+ files_to_combine.extend(glob.glob(pattern))
+ else:
+ raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,))
+
+ if strict and not files_to_combine:
+ raise CoverageException("No data to combine")
+
+ files_combined = 0
+ for f in files_to_combine:
+ if f == data.data_filename():
+ # Sometimes we are combining into a file which is one of the
+ # parallel files. Skip that file.
+ if data._debug.should('dataio'):
+ data._debug.write("Skipping combining ourself: %r" % (f,))
+ continue
+ if data._debug.should('dataio'):
+ data._debug.write("Combining data file %r" % (f,))
+ try:
+ new_data = CoverageData(f, debug=data._debug)
+ new_data.read()
+ except CoverageException as exc:
+ if data._warn:
+ # The CoverageException has the file name in it, so just
+ # use the message as the warning.
+ data._warn(str(exc))
+ 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 strict and not files_combined:
+ raise CoverageException("No usable data files")
diff --git a/third_party/python/coverage/coverage/debug.py b/third_party/python/coverage/coverage/debug.py
new file mode 100644
index 0000000000..194f16f50d
--- /dev/null
+++ b/third_party/python/coverage/coverage/debug.py
@@ -0,0 +1,406 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Control of and utilities for debugging."""
+
+import contextlib
+import functools
+import inspect
+import itertools
+import os
+import pprint
+import sys
+try:
+ import _thread
+except ImportError:
+ import thread as _thread
+
+from coverage.backward import reprlib, StringIO
+from coverage.misc import isolate_module
+
+os = isolate_module(os)
+
+
+# When debugging, it can be helpful to force some options, especially when
+# debugging the configuration mechanisms you usually use to control debugging!
+# This is a list of forced debugging options.
+FORCED_DEBUG = []
+FORCED_DEBUG_FILE = None
+
+
+class DebugControl(object):
+ """Control and output for debugging."""
+
+ show_repr_attr = False # For SimpleReprMixin
+
+ def __init__(self, options, output):
+ """Configure the options and output file for debugging."""
+ self.options = list(options) + FORCED_DEBUG
+ self.suppress_callers = False
+
+ filters = []
+ if self.should('pid'):
+ filters.append(add_pid_and_tid)
+ self.output = DebugOutputFile.get_one(
+ output,
+ show_process=self.should('process'),
+ filters=filters,
+ )
+ self.raw_output = self.output.outfile
+
+ def __repr__(self):
+ return "<DebugControl options=%r raw_output=%r>" % (self.options, self.raw_output)
+
+ def should(self, option):
+ """Decide whether to output debug information in category `option`."""
+ if option == "callers" and self.suppress_callers:
+ return False
+ return (option in self.options)
+
+ @contextlib.contextmanager
+ def without_callers(self):
+ """A context manager to prevent call stacks from being logged."""
+ old = self.suppress_callers
+ self.suppress_callers = True
+ try:
+ yield
+ finally:
+ self.suppress_callers = old
+
+ def write(self, msg):
+ """Write a line of debug output.
+
+ `msg` is the line to write. A newline will be appended.
+
+ """
+ self.output.write(msg+"\n")
+ if self.should('self'):
+ caller_self = inspect.stack()[1][0].f_locals.get('self')
+ if caller_self is not None:
+ self.output.write("self: {!r}\n".format(caller_self))
+ if self.should('callers'):
+ dump_stack_frames(out=self.output, skip=1)
+ self.output.flush()
+
+
+class DebugControlString(DebugControl):
+ """A `DebugControl` that writes to a StringIO, for testing."""
+ def __init__(self, options):
+ super(DebugControlString, self).__init__(options, StringIO())
+
+ def get_output(self):
+ """Get the output text from the `DebugControl`."""
+ return self.raw_output.getvalue()
+
+
+class NoDebugging(object):
+ """A replacement for DebugControl that will never try to do anything."""
+ def should(self, option): # pylint: disable=unused-argument
+ """Should we write debug messages? Never."""
+ return False
+
+
+def info_header(label):
+ """Make a nice header string."""
+ return "--{:-<60s}".format(" "+label+" ")
+
+
+def info_formatter(info):
+ """Produce a sequence of formatted lines from info.
+
+ `info` is a sequence of pairs (label, data). The produced lines are
+ nicely formatted, ready to print.
+
+ """
+ info = list(info)
+ if not info:
+ return
+ label_len = 30
+ assert all(len(l) < label_len for l, _ in info)
+ for label, data in info:
+ if data == []:
+ data = "-none-"
+ if isinstance(data, (list, set, tuple)):
+ prefix = "%*s:" % (label_len, label)
+ for e in data:
+ yield "%*s %s" % (label_len+1, prefix, e)
+ prefix = ""
+ else:
+ yield "%*s: %s" % (label_len, label, data)
+
+
+def write_formatted_info(writer, header, info):
+ """Write a sequence of (label,data) pairs nicely."""
+ writer.write(info_header(header))
+ for line in info_formatter(info):
+ writer.write(" %s" % line)
+
+
+def short_stack(limit=None, skip=0):
+ """Return a string summarizing the call stack.
+
+ The string is multi-line, with one line per stack frame. Each line shows
+ the function name, the file name, and the line number:
+
+ ...
+ start_import_stop : /Users/ned/coverage/trunk/tests/coveragetest.py @95
+ import_local_file : /Users/ned/coverage/trunk/tests/coveragetest.py @81
+ import_local_file : /Users/ned/coverage/trunk/coverage/backward.py @159
+ ...
+
+ `limit` is the number of frames to include, defaulting to all of them.
+
+ `skip` is the number of frames to skip, so that debugging functions can
+ call this and not be included in the result.
+
+ """
+ stack = inspect.stack()[limit:skip:-1]
+ return "\n".join("%30s : %s:%d" % (t[3], t[1], t[2]) for t in stack)
+
+
+def dump_stack_frames(limit=None, out=None, skip=0):
+ """Print a summary of the stack to stdout, or someplace else."""
+ out = out or sys.stdout
+ out.write(short_stack(limit=limit, skip=skip+1))
+ out.write("\n")
+
+
+def clipped_repr(text, numchars=50):
+ """`repr(text)`, but limited to `numchars`."""
+ r = reprlib.Repr()
+ r.maxstring = numchars
+ return r.repr(text)
+
+
+def short_id(id64):
+ """Given a 64-bit id, make a shorter 16-bit one."""
+ id16 = 0
+ for offset in range(0, 64, 16):
+ id16 ^= id64 >> offset
+ return id16 & 0xFFFF
+
+
+def add_pid_and_tid(text):
+ """A filter to add pid and tid to debug messages."""
+ # Thread ids are useful, but too long. Make a shorter one.
+ tid = "{:04x}".format(short_id(_thread.get_ident()))
+ text = "{:5d}.{}: {}".format(os.getpid(), tid, text)
+ return text
+
+
+class SimpleReprMixin(object):
+ """A mixin implementing a simple __repr__."""
+ simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id']
+
+ def __repr__(self):
+ show_attrs = (
+ (k, v) for k, v in self.__dict__.items()
+ if getattr(v, "show_repr_attr", True)
+ and not callable(v)
+ and k not in self.simple_repr_ignore
+ )
+ return "<{klass} @0x{id:x} {attrs}>".format(
+ klass=self.__class__.__name__,
+ id=id(self),
+ attrs=" ".join("{}={!r}".format(k, v) for k, v in show_attrs),
+ )
+
+
+def simplify(v): # pragma: debugging
+ """Turn things which are nearly dict/list/etc into dict/list/etc."""
+ if isinstance(v, dict):
+ return {k:simplify(vv) for k, vv in v.items()}
+ elif isinstance(v, (list, tuple)):
+ return type(v)(simplify(vv) for vv in v)
+ elif hasattr(v, "__dict__"):
+ return simplify({'.'+k: v for k, v in v.__dict__.items()})
+ else:
+ return v
+
+
+def pp(v): # pragma: debugging
+ """Debug helper to pretty-print data, including SimpleNamespace objects."""
+ # Might not be needed in 3.9+
+ pprint.pprint(simplify(v))
+
+
+def filter_text(text, filters):
+ """Run `text` through a series of filters.
+
+ `filters` is a list of functions. Each takes a string and returns a
+ string. Each is run in turn.
+
+ Returns: the final string that results after all of the filters have
+ run.
+
+ """
+ clean_text = text.rstrip()
+ ending = text[len(clean_text):]
+ text = clean_text
+ for fn in filters:
+ lines = []
+ for line in text.splitlines():
+ lines.extend(fn(line).splitlines())
+ text = "\n".join(lines)
+ return text + ending
+
+
+class CwdTracker(object): # pragma: debugging
+ """A class to add cwd info to debug messages."""
+ def __init__(self):
+ self.cwd = None
+
+ def filter(self, text):
+ """Add a cwd message for each new cwd."""
+ cwd = os.getcwd()
+ if cwd != self.cwd:
+ text = "cwd is now {!r}\n".format(cwd) + text
+ self.cwd = cwd
+ return text
+
+
+class DebugOutputFile(object): # pragma: debugging
+ """A file-like object that includes pid and cwd information."""
+ def __init__(self, outfile, show_process, filters):
+ self.outfile = outfile
+ self.show_process = show_process
+ self.filters = list(filters)
+
+ if self.show_process:
+ self.filters.insert(0, CwdTracker().filter)
+ self.write("New process: executable: %r\n" % (sys.executable,))
+ self.write("New process: cmd: %r\n" % (getattr(sys, 'argv', None),))
+ if hasattr(os, 'getppid'):
+ self.write("New process: pid: %r, parent pid: %r\n" % (os.getpid(), os.getppid()))
+
+ SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
+
+ @classmethod
+ def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False):
+ """Get a DebugOutputFile.
+
+ If `fileobj` is provided, then a new DebugOutputFile is made with it.
+
+ If `fileobj` isn't provided, then a file is chosen
+ (COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton
+ DebugOutputFile is made.
+
+ `show_process` controls whether the debug file adds process-level
+ information, and filters is a list of other message filters to apply.
+
+ `filters` are the text filters to apply to the stream to annotate with
+ pids, etc.
+
+ If `interim` is true, then a future `get_one` can replace this one.
+
+ """
+ if fileobj is not None:
+ # Make DebugOutputFile around the fileobj passed.
+ return cls(fileobj, show_process, filters)
+
+ # Because of the way igor.py deletes and re-imports modules,
+ # this class can be defined more than once. But we really want
+ # a process-wide singleton. So stash it in sys.modules instead of
+ # on a class attribute. Yes, this is aggressively gross.
+ the_one, is_interim = sys.modules.get(cls.SYS_MOD_NAME, (None, True))
+ if the_one is None or is_interim:
+ if fileobj is None:
+ debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
+ if debug_file_name:
+ fileobj = open(debug_file_name, "a")
+ else:
+ fileobj = sys.stderr
+ the_one = cls(fileobj, show_process, filters)
+ sys.modules[cls.SYS_MOD_NAME] = (the_one, interim)
+ return the_one
+
+ def write(self, text):
+ """Just like file.write, but filter through all our filters."""
+ self.outfile.write(filter_text(text, self.filters))
+ self.outfile.flush()
+
+ def flush(self):
+ """Flush our file."""
+ self.outfile.flush()
+
+
+def log(msg, stack=False): # pragma: debugging
+ """Write a log message as forcefully as possible."""
+ out = DebugOutputFile.get_one(interim=True)
+ out.write(msg+"\n")
+ if stack:
+ dump_stack_frames(out=out, skip=1)
+
+
+def decorate_methods(decorator, butnot=(), private=False): # pragma: debugging
+ """A class decorator to apply a decorator to methods."""
+ def _decorator(cls):
+ for name, meth in inspect.getmembers(cls, inspect.isroutine):
+ if name not in cls.__dict__:
+ continue
+ if name != "__init__":
+ if not private and name.startswith("_"):
+ continue
+ if name in butnot:
+ continue
+ setattr(cls, name, decorator(meth))
+ return cls
+ return _decorator
+
+
+def break_in_pudb(func): # pragma: debugging
+ """A function decorator to stop in the debugger for each call."""
+ @functools.wraps(func)
+ def _wrapper(*args, **kwargs):
+ import pudb
+ sys.stdout = sys.__stdout__
+ pudb.set_trace()
+ return func(*args, **kwargs)
+ return _wrapper
+
+
+OBJ_IDS = itertools.count()
+CALLS = itertools.count()
+OBJ_ID_ATTR = "$coverage.object_id"
+
+def show_calls(show_args=True, show_stack=False, show_return=False): # pragma: debugging
+ """A method decorator to debug-log each call to the function."""
+ def _decorator(func):
+ @functools.wraps(func)
+ def _wrapper(self, *args, **kwargs):
+ oid = getattr(self, OBJ_ID_ATTR, None)
+ if oid is None:
+ oid = "{:08d} {:04d}".format(os.getpid(), next(OBJ_IDS))
+ setattr(self, OBJ_ID_ATTR, oid)
+ extra = ""
+ if show_args:
+ eargs = ", ".join(map(repr, args))
+ ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items())
+ extra += "("
+ extra += eargs
+ if eargs and ekwargs:
+ extra += ", "
+ extra += ekwargs
+ extra += ")"
+ if show_stack:
+ extra += " @ "
+ extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines())
+ callid = next(CALLS)
+ msg = "{} {:04d} {}{}\n".format(oid, callid, func.__name__, extra)
+ DebugOutputFile.get_one(interim=True).write(msg)
+ ret = func(self, *args, **kwargs)
+ if show_return:
+ msg = "{} {:04d} {} return {!r}\n".format(oid, callid, func.__name__, ret)
+ DebugOutputFile.get_one(interim=True).write(msg)
+ return ret
+ return _wrapper
+ return _decorator
+
+
+def _clean_stack_line(s): # pragma: debugging
+ """Simplify some paths in a stack trace, for compactness."""
+ s = s.strip()
+ s = s.replace(os.path.dirname(__file__) + '/', '')
+ s = s.replace(os.path.dirname(os.__file__) + '/', '')
+ s = s.replace(sys.prefix + '/', '')
+ return s
diff --git a/third_party/python/coverage/coverage/disposition.py b/third_party/python/coverage/coverage/disposition.py
new file mode 100644
index 0000000000..9b9a997d8a
--- /dev/null
+++ b/third_party/python/coverage/coverage/disposition.py
@@ -0,0 +1,37 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Simple value objects for tracking what to do with files."""
+
+
+class FileDisposition(object):
+ """A simple value type for recording what to do with a file."""
+ pass
+
+
+# FileDisposition "methods": FileDisposition is a pure value object, so it can
+# be implemented in either C or Python. Acting on them is done with these
+# functions.
+
+def disposition_init(cls, original_filename):
+ """Construct and initialize a new FileDisposition object."""
+ disp = cls()
+ disp.original_filename = original_filename
+ disp.canonical_filename = original_filename
+ disp.source_filename = None
+ disp.trace = False
+ disp.reason = ""
+ disp.file_tracer = None
+ disp.has_dynamic_filename = False
+ return disp
+
+
+def disposition_debug_msg(disp):
+ """Make a nice debug message of what the FileDisposition is doing."""
+ if disp.trace:
+ msg = "Tracing %r" % (disp.original_filename,)
+ if disp.file_tracer:
+ msg += ": will be traced by %r" % disp.file_tracer
+ else:
+ msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason)
+ return msg
diff --git a/third_party/python/coverage/coverage/env.py b/third_party/python/coverage/coverage/env.py
new file mode 100644
index 0000000000..b5da3b4719
--- /dev/null
+++ b/third_party/python/coverage/coverage/env.py
@@ -0,0 +1,99 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Determine facts about the environment."""
+
+import os
+import platform
+import sys
+
+# Operating systems.
+WINDOWS = sys.platform == "win32"
+LINUX = sys.platform.startswith("linux")
+
+# 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
+class PYBEHAVIOR(object):
+ """Flags indicating this Python's behavior."""
+
+ # Is "if __debug__" optimized away?
+ optimize_if_debug = (not PYPY)
+
+ # Is "if not __debug__" optimized away?
+ optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4))
+
+ # Is "if not __debug__" optimized away even better?
+ optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1))
+
+ # Do we have yield-from?
+ yield_from = (PYVERSION >= (3, 3))
+
+ # Do we have PEP 420 namespace packages?
+ namespaces_pep420 = (PYVERSION >= (3, 3))
+
+ # Do .pyc files have the source file size recorded in them?
+ size_in_pyc = (PYVERSION >= (3, 3))
+
+ # Do we have async and await syntax?
+ async_syntax = (PYVERSION >= (3, 5))
+
+ # PEP 448 defined additional unpacking generalizations
+ unpackings_pep448 = (PYVERSION >= (3, 5))
+
+ # Can co_lnotab have negative deltas?
+ 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))
+
+ # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It
+ # 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 = (PYVERSION >= (3, 7, 0, 'beta', 3))
+
+ # 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))
+
+ # 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
+ # (old behavior)?
+ trace_decorated_def = (PYVERSION >= (3, 8))
+
+ # Are while-true loops optimized into absolute jumps with no loop setup?
+ nix_while_true = (PYVERSION >= (3, 8))
+
+ # Python 3.9a1 made sys.argv[0] and other reported files absolute paths.
+ report_absolute_files = (PYVERSION >= (3, 9))
+
+# Coverage.py specifics.
+
+# Are we using the C-implemented trace function?
+C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c'
+
+# Are we coverage-measuring ourselves?
+METACOV = os.getenv('COVERAGE_COVERAGE', '') != ''
+
+# Are we running our test suite?
+# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
+# test-specific behavior like contracts.
+TESTING = os.getenv('COVERAGE_TESTING', '') == 'True'
diff --git a/third_party/python/coverage/coverage/execfile.py b/third_party/python/coverage/coverage/execfile.py
new file mode 100644
index 0000000000..29409d517a
--- /dev/null
+++ b/third_party/python/coverage/coverage/execfile.py
@@ -0,0 +1,362 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Execute files of Python code."""
+
+import inspect
+import marshal
+import os
+import struct
+import sys
+import types
+
+from coverage import env
+from coverage.backward import BUILTINS
+from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec
+from coverage.files import canonical_filename, python_reported_file
+from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module
+from coverage.phystokens import compile_unicode
+from coverage.python import get_python_source
+
+os = isolate_module(os)
+
+
+class DummyLoader(object):
+ """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.
+
+ Currently only implements the .fullname attribute
+ """
+ def __init__(self, fullname, *_args):
+ self.fullname = fullname
+
+
+if importlib_util_find_spec:
+ def find_module(modulename):
+ """Find the module named `modulename`.
+
+ Returns the file path of the module, the name of the enclosing
+ package, and the spec.
+ """
+ try:
+ spec = importlib_util_find_spec(modulename)
+ except ImportError as err:
+ raise NoSource(str(err))
+ if not spec:
+ raise NoSource("No module named %r" % (modulename,))
+ pathname = spec.origin
+ packagename = spec.name
+ if spec.submodule_search_locations:
+ mod_main = modulename + ".__main__"
+ spec = importlib_util_find_spec(mod_main)
+ if not spec:
+ raise NoSource(
+ "No module named %s; "
+ "%r is a package and cannot be directly executed"
+ % (mod_main, modulename)
+ )
+ pathname = spec.origin
+ packagename = spec.name
+ packagename = packagename.rpartition(".")[0]
+ return pathname, packagename, spec
+else:
+ def find_module(modulename):
+ """Find the module named `modulename`.
+
+ Returns the file path of the module, the name of the enclosing
+ package, and None (where a spec would have been).
+ """
+ openfile = None
+ glo, loc = globals(), locals()
+ try:
+ # Search for the module - inside its parent package, if any - using
+ # standard import mechanics.
+ if '.' in modulename:
+ packagename, name = modulename.rsplit('.', 1)
+ package = __import__(packagename, glo, loc, ['__path__'])
+ searchpath = package.__path__
+ else:
+ packagename, name = None, modulename
+ searchpath = None # "top-level search" in imp.find_module()
+ openfile, pathname, _ = imp.find_module(name, searchpath)
+
+ # Complain if this is a magic non-file module.
+ if openfile is None and pathname is None:
+ raise NoSource(
+ "module does not live in a file: %r" % modulename
+ )
+
+ # If `modulename` is actually a package, not a mere module, then we
+ # pretend to be Python 2.7 and try running its __main__.py script.
+ if openfile is None:
+ packagename = modulename
+ name = '__main__'
+ package = __import__(packagename, glo, loc, ['__path__'])
+ searchpath = package.__path__
+ openfile, pathname, _ = imp.find_module(name, searchpath)
+ except ImportError as err:
+ raise NoSource(str(err))
+ finally:
+ if openfile:
+ openfile.close()
+
+ return pathname, packagename, None
+
+
+class PyRunner(object):
+ """Multi-stage execution of Python code.
+
+ This is meant to emulate real Python execution as closely as possible.
+
+ """
+ def __init__(self, args, as_module=False):
+ self.args = args
+ self.as_module = as_module
+
+ self.arg0 = args[0]
+ self.package = self.modulename = self.pathname = self.loader = self.spec = None
+
+ def prepare(self):
+ """Set sys.path properly.
+
+ This needs to happen before any importing, and without importing anything.
+ """
+ if self.as_module:
+ if env.PYBEHAVIOR.actual_syspath0_dash_m:
+ path0 = os.getcwd()
+ else:
+ path0 = ""
+ elif os.path.isdir(self.arg0):
+ # Running a directory means running the __main__.py file in that
+ # directory.
+ path0 = self.arg0
+ else:
+ path0 = os.path.abspath(os.path.dirname(self.arg0))
+
+ if os.path.isdir(sys.path[0]):
+ # sys.path fakery. If we are being run as a command, then sys.path[0]
+ # is the directory of the "coverage" script. If this is so, replace
+ # sys.path[0] with the directory of the file we're running, or the
+ # current directory when running modules. If it isn't so, then we
+ # don't know what's going on, and just leave it alone.
+ top_file = inspect.stack()[-1][0].f_code.co_filename
+ sys_path_0_abs = os.path.abspath(sys.path[0])
+ top_file_dir_abs = os.path.abspath(os.path.dirname(top_file))
+ sys_path_0_abs = canonical_filename(sys_path_0_abs)
+ top_file_dir_abs = canonical_filename(top_file_dir_abs)
+ if sys_path_0_abs != top_file_dir_abs:
+ path0 = None
+
+ else:
+ # sys.path[0] is a file. Is the next entry the directory containing
+ # that file?
+ if sys.path[1] == os.path.dirname(sys.path[0]):
+ # Can it be right to always remove that?
+ del sys.path[1]
+
+ if path0 is not None:
+ sys.path[0] = python_reported_file(path0)
+
+ def _prepare2(self):
+ """Do more preparation to run Python code.
+
+ Includes finding the module to run and adjusting sys.argv[0].
+ This method is allowed to import code.
+
+ """
+ if self.as_module:
+ self.modulename = self.arg0
+ pathname, self.package, self.spec = find_module(self.modulename)
+ if self.spec is not None:
+ self.modulename = self.spec.name
+ self.loader = DummyLoader(self.modulename)
+ self.pathname = os.path.abspath(pathname)
+ self.args[0] = self.arg0 = self.pathname
+ elif os.path.isdir(self.arg0):
+ # Running a directory means running the __main__.py file in that
+ # directory.
+ for ext in [".py", ".pyc", ".pyo"]:
+ try_filename = os.path.join(self.arg0, "__main__" + ext)
+ if os.path.exists(try_filename):
+ self.arg0 = try_filename
+ break
+ else:
+ raise NoSource("Can't find '__main__' module in '%s'" % self.arg0)
+
+ if env.PY2:
+ self.arg0 = os.path.abspath(self.arg0)
+
+ # Make a spec. I don't know if this is the right way to do it.
+ try:
+ import importlib.machinery
+ except ImportError:
+ pass
+ else:
+ try_filename = python_reported_file(try_filename)
+ self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename)
+ self.spec.has_location = True
+ self.package = ""
+ self.loader = DummyLoader("__main__")
+ else:
+ if env.PY3:
+ self.loader = DummyLoader("__main__")
+
+ self.arg0 = python_reported_file(self.arg0)
+
+ def run(self):
+ """Run the Python code!"""
+
+ self._prepare2()
+
+ # Create a module to serve as __main__
+ main_mod = types.ModuleType('__main__')
+
+ from_pyc = self.arg0.endswith((".pyc", ".pyo"))
+ main_mod.__file__ = self.arg0
+ if from_pyc:
+ main_mod.__file__ = main_mod.__file__[:-1]
+ if self.package is not None:
+ main_mod.__package__ = self.package
+ main_mod.__loader__ = self.loader
+ if self.spec is not None:
+ main_mod.__spec__ = self.spec
+
+ main_mod.__builtins__ = BUILTINS
+
+ sys.modules['__main__'] = main_mod
+
+ # Set sys.argv properly.
+ sys.argv = self.args
+
+ try:
+ # Make a code object somehow.
+ if from_pyc:
+ code = make_code_from_pyc(self.arg0)
+ else:
+ code = make_code_from_py(self.arg0)
+ except CoverageException:
+ raise
+ except Exception as exc:
+ msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}"
+ raise CoverageException(msg.format(filename=self.arg0, exc=exc))
+
+ # Execute the code object.
+ # Return to the original directory in case the test code exits in
+ # a non-existent directory.
+ cwd = os.getcwd()
+ try:
+ exec(code, main_mod.__dict__)
+ except SystemExit: # pylint: disable=try-except-raise
+ # The user called sys.exit(). Just pass it along to the upper
+ # layers, where it will be handled.
+ raise
+ except Exception:
+ # Something went wrong while executing the user code.
+ # Get the exc_info, and pack them into an exception that we can
+ # throw up to the outer loop. We peel one layer off the traceback
+ # so that the coverage.py code doesn't appear in the final printed
+ # traceback.
+ typ, err, tb = sys.exc_info()
+
+ # PyPy3 weirdness. If I don't access __context__, then somehow it
+ # is non-None when the exception is reported at the upper layer,
+ # and a nested exception is shown to the user. This getattr fixes
+ # it somehow? https://bitbucket.org/pypy/pypy/issue/1903
+ getattr(err, '__context__', None)
+
+ # Call the excepthook.
+ try:
+ if hasattr(err, "__traceback__"):
+ err.__traceback__ = err.__traceback__.tb_next
+ sys.excepthook(typ, err, tb.tb_next)
+ except SystemExit: # pylint: disable=try-except-raise
+ raise
+ except Exception:
+ # Getting the output right in the case of excepthook
+ # shenanigans is kind of involved.
+ sys.stderr.write("Error in sys.excepthook:\n")
+ typ2, err2, tb2 = sys.exc_info()
+ err2.__suppress_context__ = True
+ if hasattr(err2, "__traceback__"):
+ err2.__traceback__ = err2.__traceback__.tb_next
+ sys.__excepthook__(typ2, err2, tb2.tb_next)
+ sys.stderr.write("\nOriginal exception was:\n")
+ raise ExceptionDuringRun(typ, err, tb.tb_next)
+ else:
+ sys.exit(1)
+ finally:
+ os.chdir(cwd)
+
+
+def run_python_module(args):
+ """Run a Python module, as though with ``python -m name args...``.
+
+ `args` is the argument array to present as sys.argv, including the first
+ element naming the module being executed.
+
+ This is a helper for tests, to encapsulate how to use PyRunner.
+
+ """
+ runner = PyRunner(args, as_module=True)
+ runner.prepare()
+ runner.run()
+
+
+def run_python_file(args):
+ """Run a Python file as if it were the main program on the command line.
+
+ `args` is the argument array to present as sys.argv, including the first
+ element naming the file being executed. `package` is the name of the
+ enclosing package, if any.
+
+ This is a helper for tests, to encapsulate how to use PyRunner.
+
+ """
+ runner = PyRunner(args, as_module=False)
+ runner.prepare()
+ runner.run()
+
+
+def make_code_from_py(filename):
+ """Get source from `filename` and make a code object of it."""
+ # Open the source file.
+ try:
+ source = get_python_source(filename)
+ except (IOError, NoSource):
+ raise NoSource("No file to run: '%s'" % filename)
+
+ code = compile_unicode(source, filename, "exec")
+ return code
+
+
+def make_code_from_pyc(filename):
+ """Get a code object from a .pyc file."""
+ try:
+ fpyc = open(filename, "rb")
+ except IOError:
+ raise NoCode("No file to run: '%s'" % filename)
+
+ with fpyc:
+ # First four bytes are a version-specific magic number. It has to
+ # match or we won't run the file.
+ magic = fpyc.read(4)
+ if magic != PYC_MAGIC_NUMBER:
+ raise NoCode("Bad magic number in .pyc file: {} != {}".format(magic, PYC_MAGIC_NUMBER))
+
+ date_based = True
+ if env.PYBEHAVIOR.hashed_pyc_pep552:
+ flags = struct.unpack('<L', fpyc.read(4))[0]
+ hash_based = flags & 0x01
+ if hash_based:
+ fpyc.read(8) # Skip the hash.
+ date_based = False
+ if date_based:
+ # Skip the junk in the header that we don't need.
+ fpyc.read(4) # Skip the moddate.
+ if env.PYBEHAVIOR.size_in_pyc:
+ # 3.3 added another long to the header (size), skip it.
+ fpyc.read(4)
+
+ # The rest of the file is the code object we want.
+ code = marshal.load(fpyc)
+
+ return code
diff --git a/third_party/python/coverage/coverage/files.py b/third_party/python/coverage/coverage/files.py
new file mode 100644
index 0000000000..5c2ff1ace4
--- /dev/null
+++ b/third_party/python/coverage/coverage/files.py
@@ -0,0 +1,432 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""File wrangling."""
+
+import hashlib
+import fnmatch
+import ntpath
+import os
+import os.path
+import posixpath
+import re
+import sys
+
+from coverage import env
+from coverage.backward import unicode_class
+from coverage.misc import contract, CoverageException, join_regex, isolate_module
+
+
+os = isolate_module(os)
+
+
+def set_relative_directory():
+ """Set the directory that `relative_filename` will be relative to."""
+ global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
+
+ # The absolute path to our current directory.
+ RELATIVE_DIR = os.path.normcase(abs_file(os.curdir) + os.sep)
+
+ # Cache of results of calling the canonical_filename() method, to
+ # avoid duplicating work.
+ CANONICAL_FILENAME_CACHE = {}
+
+
+def relative_directory():
+ """Return the directory that `relative_filename` is relative to."""
+ return RELATIVE_DIR
+
+
+@contract(returns='unicode')
+def relative_filename(filename):
+ """Return the relative form of `filename`.
+
+ The file name will be relative to the current directory when the
+ `set_relative_directory` was called.
+
+ """
+ fnorm = os.path.normcase(filename)
+ if fnorm.startswith(RELATIVE_DIR):
+ filename = filename[len(RELATIVE_DIR):]
+ return unicode_filename(filename)
+
+
+@contract(returns='unicode')
+def canonical_filename(filename):
+ """Return a canonical file name for `filename`.
+
+ An absolute path with no redundant components and normalized case.
+
+ """
+ if filename not in CANONICAL_FILENAME_CACHE:
+ cf = filename
+ if not os.path.isabs(filename):
+ for path in [os.curdir] + sys.path:
+ if path is None:
+ continue
+ f = os.path.join(path, filename)
+ try:
+ exists = os.path.exists(f)
+ except UnicodeError:
+ exists = False
+ if exists:
+ cf = f
+ break
+ cf = abs_file(cf)
+ CANONICAL_FILENAME_CACHE[filename] = cf
+ return CANONICAL_FILENAME_CACHE[filename]
+
+
+MAX_FLAT = 200
+
+@contract(filename='unicode', returns='unicode')
+def flat_rootname(filename):
+ """A base for a flat file name to correspond to this file.
+
+ Useful for writing files about the code where you want all the files in
+ the same directory, but need to differentiate same-named files from
+ different directories.
+
+ For example, the file a/b/c.py will return 'a_b_c_py'
+
+ """
+ name = ntpath.splitdrive(filename)[1]
+ name = re.sub(r"[\\/.:]", "_", name)
+ if len(name) > MAX_FLAT:
+ h = hashlib.sha1(name.encode('UTF-8')).hexdigest()
+ name = name[-(MAX_FLAT-len(h)-1):] + '_' + h
+ return name
+
+
+if env.WINDOWS:
+
+ _ACTUAL_PATH_CACHE = {}
+ _ACTUAL_PATH_LIST_CACHE = {}
+
+ def actual_path(path):
+ """Get the actual path of `path`, including the correct case."""
+ if env.PY2 and isinstance(path, unicode_class):
+ path = path.encode(sys.getfilesystemencoding())
+ if path in _ACTUAL_PATH_CACHE:
+ return _ACTUAL_PATH_CACHE[path]
+
+ head, tail = os.path.split(path)
+ if not tail:
+ # This means head is the drive spec: normalize it.
+ actpath = head.upper()
+ elif not head:
+ actpath = tail
+ else:
+ head = actual_path(head)
+ if head in _ACTUAL_PATH_LIST_CACHE:
+ files = _ACTUAL_PATH_LIST_CACHE[head]
+ else:
+ try:
+ files = os.listdir(head)
+ except Exception:
+ # This will raise OSError, or this bizarre TypeError:
+ # https://bugs.python.org/issue1776160
+ files = []
+ _ACTUAL_PATH_LIST_CACHE[head] = files
+ normtail = os.path.normcase(tail)
+ for f in files:
+ if os.path.normcase(f) == normtail:
+ tail = f
+ break
+ actpath = os.path.join(head, tail)
+ _ACTUAL_PATH_CACHE[path] = actpath
+ return actpath
+
+else:
+ def actual_path(filename):
+ """The actual path for non-Windows platforms."""
+ return filename
+
+
+if env.PY2:
+ @contract(returns='unicode')
+ def unicode_filename(filename):
+ """Return a Unicode version of `filename`."""
+ if isinstance(filename, str):
+ encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
+ filename = filename.decode(encoding, "replace")
+ return filename
+else:
+ @contract(filename='unicode', returns='unicode')
+ def unicode_filename(filename):
+ """Return a Unicode version of `filename`."""
+ return filename
+
+
+@contract(returns='unicode')
+def abs_file(path):
+ """Return the absolute normalized form of `path`."""
+ try:
+ path = os.path.realpath(path)
+ except UnicodeError:
+ pass
+ path = os.path.abspath(path)
+ path = actual_path(path)
+ path = unicode_filename(path)
+ return path
+
+
+def python_reported_file(filename):
+ """Return the string as Python would describe this file name."""
+ if env.PYBEHAVIOR.report_absolute_files:
+ filename = os.path.abspath(filename)
+ return filename
+
+
+RELATIVE_DIR = None
+CANONICAL_FILENAME_CACHE = None
+set_relative_directory()
+
+
+def isabs_anywhere(filename):
+ """Is `filename` an absolute path on any OS?"""
+ return ntpath.isabs(filename) or posixpath.isabs(filename)
+
+
+def prep_patterns(patterns):
+ """Prepare the file patterns for use in a `FnmatchMatcher`.
+
+ If a pattern starts with a wildcard, it is used as a pattern
+ as-is. If it does not start with a wildcard, then it is made
+ absolute with the current directory.
+
+ If `patterns` is None, an empty list is returned.
+
+ """
+ prepped = []
+ for p in patterns or []:
+ if p.startswith(("*", "?")):
+ prepped.append(p)
+ else:
+ prepped.append(abs_file(p))
+ return prepped
+
+
+class TreeMatcher(object):
+ """A matcher for files in a tree.
+
+ Construct with a list of paths, either files or directories. Paths match
+ with the `match` method if they are one of the files, or if they are
+ somewhere in a subtree rooted at one of the directories.
+
+ """
+ def __init__(self, paths):
+ self.paths = list(paths)
+
+ def __repr__(self):
+ return "<TreeMatcher %r>" % self.paths
+
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.paths
+
+ def match(self, fpath):
+ """Does `fpath` indicate a file in one of our trees?"""
+ for p in self.paths:
+ if fpath.startswith(p):
+ if fpath == p:
+ # This is the same file!
+ return True
+ if fpath[len(p)] == os.sep:
+ # This is a file in the directory
+ return True
+ return False
+
+
+class ModuleMatcher(object):
+ """A matcher for modules in a tree."""
+ def __init__(self, module_names):
+ self.modules = list(module_names)
+
+ def __repr__(self):
+ return "<ModuleMatcher %r>" % (self.modules)
+
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.modules
+
+ def match(self, module_name):
+ """Does `module_name` indicate a module in one of our packages?"""
+ if not module_name:
+ return False
+
+ for m in self.modules:
+ if module_name.startswith(m):
+ if module_name == m:
+ return True
+ if module_name[len(m)] == '.':
+ # This is a module in the package
+ return True
+
+ return False
+
+
+class FnmatchMatcher(object):
+ """A matcher for files by file name pattern."""
+ def __init__(self, pats):
+ self.pats = list(pats)
+ self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS)
+
+ def __repr__(self):
+ return "<FnmatchMatcher %r>" % self.pats
+
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.pats
+
+ def match(self, fpath):
+ """Does `fpath` match one of our file name patterns?"""
+ return self.re.match(fpath) is not None
+
+
+def sep(s):
+ """Find the path separator used in this string, or os.sep if none."""
+ sep_match = re.search(r"[\\/]", s)
+ if sep_match:
+ the_sep = sep_match.group(0)
+ else:
+ the_sep = os.sep
+ return the_sep
+
+
+def fnmatches_to_regex(patterns, case_insensitive=False, partial=False):
+ """Convert fnmatch patterns to a compiled regex that matches any of them.
+
+ Slashes are always converted to match either slash or backslash, for
+ Windows support, even when running elsewhere.
+
+ If `partial` is true, then the pattern will match if the target string
+ starts with the pattern. Otherwise, it must match the entire string.
+
+ Returns: a compiled regex object. Use the .match method to compare target
+ strings.
+
+ """
+ regexes = (fnmatch.translate(pattern) for pattern in patterns)
+ # Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/",
+ # so we have to deal with maybe a backslash.
+ regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes)
+
+ if partial:
+ # fnmatch always adds a \Z to match the whole string, which we don't
+ # want, so we remove the \Z. While removing it, we only replace \Z if
+ # followed by paren (introducing flags), or at end, to keep from
+ # destroying a literal \Z in the pattern.
+ regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes)
+
+ flags = 0
+ if case_insensitive:
+ flags |= re.IGNORECASE
+ compiled = re.compile(join_regex(regexes), flags=flags)
+
+ return compiled
+
+
+class PathAliases(object):
+ """A collection of aliases for paths.
+
+ When combining data files from remote machines, often the paths to source
+ code are different, for example, due to OS differences, or because of
+ serialized checkouts on continuous integration machines.
+
+ A `PathAliases` object tracks a list of pattern/result pairs, and can
+ map a path through those aliases to produce a unified path.
+
+ """
+ def __init__(self):
+ self.aliases = []
+
+ def pprint(self): # pragma: debugging
+ """Dump the important parts of the PathAliases, for debugging."""
+ for regex, result in self.aliases:
+ print("{!r} --> {!r}".format(regex.pattern, result))
+
+ def add(self, pattern, result):
+ """Add the `pattern`/`result` pair to the list of aliases.
+
+ `pattern` is an `fnmatch`-style pattern. `result` is a simple
+ string. When mapping paths, if a path starts with a match against
+ `pattern`, then that match is replaced with `result`. This models
+ isomorphic source trees being rooted at different places on two
+ different machines.
+
+ `pattern` can't end with a wildcard component, since that would
+ match an entire tree, and not just its root.
+
+ """
+ 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):
+ pattern = abs_file(pattern)
+ if not pattern.endswith(pattern_sep):
+ pattern += pattern_sep
+
+ # Make a regex from the pattern.
+ regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True)
+
+ # Normalize the result: it must end with a path separator.
+ result_sep = sep(result)
+ result = result.rstrip(r"\/") + result_sep
+ self.aliases.append((regex, result))
+
+ def map(self, path):
+ """Map `path` through the aliases.
+
+ `path` is checked against all of the patterns. The first pattern to
+ match is used to replace the root of the path with the result root.
+ Only one pattern is ever used. If no patterns match, `path` is
+ returned unchanged.
+
+ The separator style in the result is made to match that of the result
+ in the alias.
+
+ Returns the mapped path. If a mapping has happened, this is a
+ canonical path. If no mapping has happened, it is the original value
+ of `path` unchanged.
+
+ """
+ for regex, result in self.aliases:
+ m = regex.match(path)
+ if m:
+ new = path.replace(m.group(0), result)
+ new = new.replace(sep(path), sep(result))
+ new = canonical_filename(new)
+ return new
+ return path
+
+
+def find_python_files(dirname):
+ """Yield all of the importable Python files in `dirname`, recursively.
+
+ To be importable, the files have to be in a directory with a __init__.py,
+ except for `dirname` itself, which isn't required to have one. The
+ assumption is that `dirname` was specified directly, so the user knows
+ best, but sub-directories are checked for a __init__.py to be sure we only
+ find the importable files.
+
+ """
+ for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
+ if i > 0 and '__init__.py' not in filenames:
+ # If a directory doesn't have __init__.py, then it isn't
+ # importable and neither are its files
+ del dirnames[:]
+ continue
+ for filename in filenames:
+ # We're only interested in files that look like reasonable Python
+ # files: Must end with .py or .pyw, and must not have certain funny
+ # characters that probably mean they are editor junk.
+ if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename):
+ yield os.path.join(dirpath, filename)
diff --git a/third_party/python/coverage/coverage/fullcoverage/encodings.py b/third_party/python/coverage/coverage/fullcoverage/encodings.py
new file mode 100644
index 0000000000..aeb416e406
--- /dev/null
+++ b/third_party/python/coverage/coverage/fullcoverage/encodings.py
@@ -0,0 +1,60 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Imposter encodings module that installs a coverage-style tracer.
+
+This is NOT the encodings module; it is an imposter that sets up tracing
+instrumentation and then replaces itself with the real encodings module.
+
+If the directory that holds this file is placed first in the PYTHONPATH when
+using "coverage" to run Python's tests, then this file will become the very
+first module imported by the internals of Python 3. It installs a
+coverage.py-compatible trace function that can watch Standard Library modules
+execute from the very earliest stages of Python's own boot process. This fixes
+a problem with coverage.py - that it starts too late to trace the coverage of
+many of the most fundamental modules in the Standard Library.
+
+"""
+
+import sys
+
+class FullCoverageTracer(object):
+ def __init__(self):
+ # `traces` is a list of trace events. Frames are tricky: the same
+ # frame object is used for a whole scope, with new line numbers
+ # written into it. So in one scope, all the frame objects are the
+ # same object, and will eventually all will point to the last line
+ # executed. So we keep the line numbers alongside the frames.
+ # The list looks like:
+ #
+ # traces = [
+ # ((frame, event, arg), lineno), ...
+ # ]
+ #
+ self.traces = []
+
+ def fullcoverage_trace(self, *args):
+ frame, event, arg = args
+ self.traces.append((args, frame.f_lineno))
+ return self.fullcoverage_trace
+
+sys.settrace(FullCoverageTracer().fullcoverage_trace)
+
+# In coverage/files.py is actual_filename(), which uses glob.glob. I don't
+# understand why, but that use of glob borks everything if fullcoverage is in
+# effect. So here we make an ugly hail-mary pass to switch off glob.glob over
+# there. This means when using fullcoverage, Windows path names will not be
+# their actual case.
+
+#sys.fullcoverage = True
+
+# Finally, remove our own directory from sys.path; remove ourselves from
+# sys.modules; and re-import "encodings", which will be the real package
+# this time. Note that the delete from sys.modules dictionary has to
+# happen last, since all of the symbols in this module will become None
+# at that exact moment, including "sys".
+
+parentdir = max(filter(__file__.startswith, sys.path), key=len)
+sys.path.remove(parentdir)
+del sys.modules['encodings']
+import encodings
diff --git a/third_party/python/coverage/coverage/html.py b/third_party/python/coverage/coverage/html.py
new file mode 100644
index 0000000000..596e114351
--- /dev/null
+++ b/third_party/python/coverage/coverage/html.py
@@ -0,0 +1,511 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""HTML reporting for coverage.py."""
+
+import datetime
+import json
+import os
+import re
+import shutil
+
+import coverage
+from coverage import env
+from coverage.backward import iitems, SimpleNamespace
+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
+from coverage.report import get_analysis_to_report
+from coverage.results import Numbers
+from coverage.templite import Templite
+
+os = isolate_module(os)
+
+
+# Static files are looked for in a list of places.
+STATIC_PATH = [
+ # The place Debian puts system Javascript libraries.
+ "/usr/share/javascript",
+
+ # Our htmlfiles directory.
+ os.path.join(os.path.dirname(__file__), "htmlfiles"),
+]
+
+
+def data_filename(fname, pkgdir=""):
+ """Return the path to a data file of ours.
+
+ The file is searched for on `STATIC_PATH`, and the first place it's found,
+ is returned.
+
+ Each directory in `STATIC_PATH` is searched as-is, and also, if `pkgdir`
+ is provided, at that sub-directory.
+
+ """
+ tried = []
+ for static_dir in STATIC_PATH:
+ static_filename = os.path.join(static_dir, fname)
+ if os.path.exists(static_filename):
+ return static_filename
+ else:
+ tried.append(static_filename)
+ if pkgdir:
+ static_filename = os.path.join(static_dir, pkgdir, fname)
+ if os.path.exists(static_filename):
+ return static_filename
+ else:
+ tried.append(static_filename)
+ raise CoverageException(
+ "Couldn't find static file %r from %r, tried: %r" % (fname, os.getcwd(), tried)
+ )
+
+
+def read_data(fname):
+ """Return the contents of a data file of ours."""
+ with open(data_filename(fname)) as data_file:
+ return data_file.read()
+
+
+def write_html(fname, html):
+ """Write `html` to `fname`, properly encoded."""
+ html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n"
+ with open(fname, "wb") as fout:
+ fout.write(html.encode('ascii', 'xmlcharrefreplace'))
+
+
+class HtmlDataGeneration(object):
+ """Generate structured data to be turned into HTML reports."""
+
+ EMPTY = "(empty)"
+
+ def __init__(self, cov):
+ self.coverage = cov
+ self.config = self.coverage.config
+ data = self.coverage.get_data()
+ self.has_arcs = data.has_arcs()
+ if self.config.show_contexts:
+ if data.measured_contexts() == set([""]):
+ self.coverage._warn("No contexts were measured")
+ data.set_query_contexts(self.config.report_contexts)
+
+ def data_for_file(self, fr, analysis):
+ """Produce the data needed for one file's report."""
+ if self.has_arcs:
+ missing_branch_arcs = analysis.missing_branch_arcs()
+ arcs_executed = analysis.arcs_executed()
+
+ if self.config.show_contexts:
+ contexts_by_lineno = analysis.data.contexts_by_lineno(analysis.filename)
+
+ lines = []
+
+ for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
+ # Figure out how to mark this line.
+ category = None
+ short_annotations = []
+ long_annotations = []
+
+ if lineno in analysis.excluded:
+ category = 'exc'
+ elif lineno in analysis.missing:
+ category = 'mis'
+ elif self.has_arcs and lineno in missing_branch_arcs:
+ category = 'par'
+ for b in missing_branch_arcs[lineno]:
+ if b < 0:
+ short_annotations.append("exit")
+ else:
+ short_annotations.append(b)
+ long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed))
+ elif lineno in analysis.statements:
+ category = 'run'
+
+ contexts = contexts_label = None
+ context_list = None
+ if category and self.config.show_contexts:
+ contexts = sorted(c or self.EMPTY for c in contexts_by_lineno[lineno])
+ if contexts == [self.EMPTY]:
+ contexts_label = self.EMPTY
+ else:
+ contexts_label = "{} ctx".format(len(contexts))
+ context_list = contexts
+
+ lines.append(SimpleNamespace(
+ tokens=tokens,
+ number=lineno,
+ category=category,
+ statement=(lineno in analysis.statements),
+ contexts=contexts,
+ contexts_label=contexts_label,
+ context_list=context_list,
+ short_annotations=short_annotations,
+ long_annotations=long_annotations,
+ ))
+
+ file_data = SimpleNamespace(
+ relative_filename=fr.relative_filename(),
+ nums=analysis.numbers,
+ lines=lines,
+ )
+
+ return file_data
+
+
+class HtmlReporter(object):
+ """HTML reporting."""
+
+ # These files will be copied from the htmlfiles directory to the output
+ # directory.
+ STATIC_FILES = [
+ ("style.css", ""),
+ ("jquery.min.js", "jquery"),
+ ("jquery.ba-throttle-debounce.min.js", "jquery-throttle-debounce"),
+ ("jquery.hotkeys.js", "jquery-hotkeys"),
+ ("jquery.isonscreen.js", "jquery-isonscreen"),
+ ("jquery.tablesorter.min.js", "jquery-tablesorter"),
+ ("coverage_html.js", ""),
+ ("keybd_closed.png", ""),
+ ("keybd_open.png", ""),
+ ]
+
+ def __init__(self, cov):
+ self.coverage = cov
+ self.config = self.coverage.config
+ self.directory = self.config.html_dir
+ title = self.config.html_title
+ if env.PY2:
+ title = title.decode("utf8")
+
+ if self.config.extra_css:
+ self.extra_css = os.path.basename(self.config.extra_css)
+ else:
+ self.extra_css = None
+
+ self.data = self.coverage.get_data()
+ self.has_arcs = self.data.has_arcs()
+
+ self.file_summaries = []
+ self.all_files_nums = []
+ self.incr = IncrementalChecker(self.directory)
+ self.datagen = HtmlDataGeneration(self.coverage)
+ self.totals = Numbers()
+
+ self.template_globals = {
+ # Functions available in the templates.
+ 'escape': escape,
+ 'pair': pair,
+ 'len': len,
+
+ # Constants for this report.
+ '__url__': coverage.__url__,
+ '__version__': coverage.__version__,
+ 'title': title,
+ 'time_stamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'),
+ 'extra_css': self.extra_css,
+ 'has_arcs': self.has_arcs,
+ 'show_contexts': self.config.show_contexts,
+
+ # Constants for all reports.
+ # These css classes determine which lines are highlighted by default.
+ 'category': {
+ 'exc': 'exc show_exc',
+ 'mis': 'mis show_mis',
+ 'par': 'par run show_par',
+ 'run': 'run',
+ }
+ }
+ self.pyfile_html_source = read_data("pyfile.html")
+ self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
+
+ def report(self, morfs):
+ """Generate an HTML report for `morfs`.
+
+ `morfs` is a list of modules or file names.
+
+ """
+ # Read the status data and check that this run used the same
+ # global data as the last run.
+ self.incr.read()
+ self.incr.check_global_data(self.config, self.pyfile_html_source)
+
+ # Process all the files.
+ for fr, analysis in get_analysis_to_report(self.coverage, morfs):
+ self.html_file(fr, analysis)
+
+ if not self.all_files_nums:
+ raise CoverageException("No data to report.")
+
+ self.totals = sum(self.all_files_nums)
+
+ # Write the index file.
+ self.index_file()
+
+ self.make_local_static_report_files()
+ return self.totals.n_statements and self.totals.pc_covered
+
+ def make_local_static_report_files(self):
+ """Make local instances of static files for HTML report."""
+ # The files we provide must always be copied.
+ for static, pkgdir in self.STATIC_FILES:
+ shutil.copyfile(
+ data_filename(static, pkgdir),
+ os.path.join(self.directory, static)
+ )
+
+ # The user may have extra CSS they want copied.
+ if self.extra_css:
+ shutil.copyfile(
+ self.config.extra_css,
+ os.path.join(self.directory, self.extra_css)
+ )
+
+ def html_file(self, fr, analysis):
+ """Generate an HTML file for one source file."""
+ rootname = flat_rootname(fr.relative_filename())
+ html_filename = rootname + ".html"
+ ensure_dir(self.directory)
+ html_path = os.path.join(self.directory, html_filename)
+
+ # Get the numbers for this file.
+ nums = analysis.numbers
+ self.all_files_nums.append(nums)
+
+ if self.config.skip_covered:
+ # Don't report on 100% files.
+ no_missing_lines = (nums.n_missing == 0)
+ no_missing_branches = (nums.n_partial_branches == 0)
+ if no_missing_lines and no_missing_branches:
+ # If there's an existing file, remove it.
+ file_be_gone(html_path)
+ return
+
+ if self.config.skip_empty:
+ # Don't report on empty files.
+ if nums.n_statements == 0:
+ file_be_gone(html_path)
+ return
+
+ # Find out if the file on disk is already correct.
+ if self.incr.can_skip_file(self.data, fr, rootname):
+ self.file_summaries.append(self.incr.index_info(rootname))
+ return
+
+ # Write the HTML page for this file.
+ file_data = self.datagen.data_for_file(fr, analysis)
+ for ldata in file_data.lines:
+ # Build the HTML for the line.
+ html = []
+ for tok_type, tok_text in ldata.tokens:
+ if tok_type == "ws":
+ html.append(escape(tok_text))
+ else:
+ tok_html = escape(tok_text) or '&nbsp;'
+ html.append(
+ u'<span class="{}">{}</span>'.format(tok_type, tok_html)
+ )
+ ldata.html = ''.join(html)
+
+ if ldata.short_annotations:
+ # 202F is NARROW NO-BREAK SPACE.
+ # 219B is RIGHTWARDS ARROW WITH STROKE.
+ ldata.annotate = u",&nbsp;&nbsp; ".join(
+ u"{}&#x202F;&#x219B;&#x202F;{}".format(ldata.number, d)
+ for d in ldata.short_annotations
+ )
+ else:
+ ldata.annotate = None
+
+ if ldata.long_annotations:
+ longs = ldata.long_annotations
+ if len(longs) == 1:
+ ldata.annotate_long = longs[0]
+ else:
+ ldata.annotate_long = u"{:d} missed branches: {}".format(
+ len(longs),
+ u", ".join(
+ u"{:d}) {}".format(num, ann_long)
+ for num, ann_long in enumerate(longs, start=1)
+ ),
+ )
+ else:
+ ldata.annotate_long = None
+
+ css_classes = []
+ if ldata.category:
+ css_classes.append(self.template_globals['category'][ldata.category])
+ ldata.css_class = ' '.join(css_classes) or "pln"
+
+ html = self.source_tmpl.render(file_data.__dict__)
+ write_html(html_path, html)
+
+ # Save this file's information for the index file.
+ index_info = {
+ 'nums': nums,
+ 'html_filename': html_filename,
+ 'relative_filename': fr.relative_filename(),
+ }
+ self.file_summaries.append(index_info)
+ self.incr.set_index_info(rootname, index_info)
+
+ def index_file(self):
+ """Write the index.html file for this report."""
+ index_tmpl = Templite(read_data("index.html"), self.template_globals)
+
+ html = index_tmpl.render({
+ 'files': self.file_summaries,
+ 'totals': self.totals,
+ })
+
+ write_html(os.path.join(self.directory, "index.html"), html)
+
+ # Write the latest hashes for next time.
+ self.incr.write()
+
+
+class IncrementalChecker(object):
+ """Logic and data to support incremental reporting."""
+
+ STATUS_FILE = "status.json"
+ STATUS_FORMAT = 2
+
+ # pylint: disable=wrong-spelling-in-comment,useless-suppression
+ # The data looks like:
+ #
+ # {
+ # "format": 2,
+ # "globals": "540ee119c15d52a68a53fe6f0897346d",
+ # "version": "4.0a1",
+ # "files": {
+ # "cogapp___init__": {
+ # "hash": "e45581a5b48f879f301c0f30bf77a50c",
+ # "index": {
+ # "html_filename": "cogapp___init__.html",
+ # "relative_filename": "cogapp/__init__",
+ # "nums": [ 1, 14, 0, 0, 0, 0, 0 ]
+ # }
+ # },
+ # ...
+ # "cogapp_whiteutils": {
+ # "hash": "8504bb427fc488c4176809ded0277d51",
+ # "index": {
+ # "html_filename": "cogapp_whiteutils.html",
+ # "relative_filename": "cogapp/whiteutils",
+ # "nums": [ 1, 59, 0, 1, 28, 2, 2 ]
+ # }
+ # }
+ # }
+ # }
+
+ def __init__(self, directory):
+ self.directory = directory
+ self.reset()
+
+ def reset(self):
+ """Initialize to empty. Causes all files to be reported."""
+ self.globals = ''
+ self.files = {}
+
+ def read(self):
+ """Read the information we stored last time."""
+ usable = False
+ try:
+ status_file = os.path.join(self.directory, self.STATUS_FILE)
+ with open(status_file) as fstatus:
+ status = json.load(fstatus)
+ except (IOError, ValueError):
+ usable = False
+ else:
+ usable = True
+ if status['format'] != self.STATUS_FORMAT:
+ usable = False
+ elif status['version'] != coverage.__version__:
+ usable = False
+
+ if usable:
+ self.files = {}
+ for filename, fileinfo in iitems(status['files']):
+ fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums'])
+ self.files[filename] = fileinfo
+ self.globals = status['globals']
+ else:
+ self.reset()
+
+ def write(self):
+ """Write the current status."""
+ status_file = os.path.join(self.directory, self.STATUS_FILE)
+ files = {}
+ for filename, fileinfo in iitems(self.files):
+ fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args()
+ files[filename] = fileinfo
+
+ status = {
+ 'format': self.STATUS_FORMAT,
+ 'version': coverage.__version__,
+ 'globals': self.globals,
+ 'files': files,
+ }
+ with open(status_file, "w") as fout:
+ json.dump(status, fout, separators=(',', ':'))
+
+ def check_global_data(self, *data):
+ """Check the global data that can affect incremental reporting."""
+ m = Hasher()
+ for d in data:
+ m.update(d)
+ these_globals = m.hexdigest()
+ if self.globals != these_globals:
+ self.reset()
+ self.globals = these_globals
+
+ def can_skip_file(self, data, fr, rootname):
+ """Can we skip reporting this file?
+
+ `data` is a CoverageData object, `fr` is a `FileReporter`, and
+ `rootname` is the name being used for the file.
+ """
+ m = Hasher()
+ m.update(fr.source().encode('utf-8'))
+ add_data_to_hash(data, fr.filename, m)
+ this_hash = m.hexdigest()
+
+ that_hash = self.file_hash(rootname)
+
+ if this_hash == that_hash:
+ # Nothing has changed to require the file to be reported again.
+ return True
+ else:
+ self.set_file_hash(rootname, this_hash)
+ return False
+
+ def file_hash(self, fname):
+ """Get the hash of `fname`'s contents."""
+ return self.files.get(fname, {}).get('hash', '')
+
+ def set_file_hash(self, fname, val):
+ """Set the hash of `fname`'s contents."""
+ self.files.setdefault(fname, {})['hash'] = val
+
+ def index_info(self, fname):
+ """Get the information for index.html for `fname`."""
+ return self.files.get(fname, {}).get('index', {})
+
+ def set_index_info(self, fname, info):
+ """Set the information for index.html for `fname`."""
+ self.files.setdefault(fname, {})['index'] = info
+
+
+# Helpers for templates and generating HTML
+
+def escape(t):
+ """HTML-escape the text in `t`.
+
+ This is only suitable for HTML text, not attributes.
+
+ """
+ # Convert HTML special chars into HTML entities.
+ return t.replace("&", "&amp;").replace("<", "&lt;")
+
+
+def pair(ratio):
+ """Format a pair of numbers so JavaScript can read them in an attribute."""
+ return "%s %s" % ratio
diff --git a/third_party/python/coverage/coverage/htmlfiles/coverage_html.js b/third_party/python/coverage/coverage/htmlfiles/coverage_html.js
new file mode 100644
index 0000000000..3bf04bf927
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/coverage_html.js
@@ -0,0 +1,589 @@
+// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+// Coverage.py HTML report browser code.
+/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
+/*global coverage: true, document, window, $ */
+
+coverage = {};
+
+// Find all the elements with shortkey_* class, and use them to assign a shortcut key.
+coverage.assign_shortkeys = function () {
+ $("*[class*='shortkey_']").each(function (i, e) {
+ $.each($(e).attr("class").split(" "), function (i, c) {
+ if (/^shortkey_/.test(c)) {
+ $(document).bind('keydown', c.substr(9), function () {
+ $(e).click();
+ });
+ }
+ });
+ });
+};
+
+// Create the events for the help panel.
+coverage.wire_up_help_panel = function () {
+ $("#keyboard_icon").click(function () {
+ // Show the help panel, and position it so the keyboard icon in the
+ // panel is in the same place as the keyboard icon in the header.
+ $(".help_panel").show();
+ var koff = $("#keyboard_icon").offset();
+ var poff = $("#panel_icon").position();
+ $(".help_panel").offset({
+ top: koff.top-poff.top,
+ left: koff.left-poff.left
+ });
+ });
+ $("#panel_icon").click(function () {
+ $(".help_panel").hide();
+ });
+};
+
+// Create the events for the filter box.
+coverage.wire_up_filter = function () {
+ // Cache elements.
+ var table = $("table.index");
+ var table_rows = table.find("tbody tr");
+ var table_row_names = table_rows.find("td.name a");
+ var no_rows = $("#no_rows");
+
+ // Create a duplicate table footer that we can modify with dynamic summed values.
+ var table_footer = $("table.index tfoot tr");
+ var table_dynamic_footer = table_footer.clone();
+ table_dynamic_footer.attr('class', 'total_dynamic hidden');
+ table_footer.after(table_dynamic_footer);
+
+ // Observe filter keyevents.
+ $("#filter").on("keyup change", $.debounce(150, function (event) {
+ var filter_value = $(this).val();
+
+ if (filter_value === "") {
+ // Filter box is empty, remove all filtering.
+ table_rows.removeClass("hidden");
+
+ // Show standard footer, hide dynamic footer.
+ table_footer.removeClass("hidden");
+ table_dynamic_footer.addClass("hidden");
+
+ // Hide placeholder, show table.
+ if (no_rows.length > 0) {
+ no_rows.hide();
+ }
+ table.show();
+
+ }
+ else {
+ // Filter table items by value.
+ var hidden = 0;
+ var shown = 0;
+
+ // Hide / show elements.
+ $.each(table_row_names, function () {
+ var element = $(this).parents("tr");
+
+ if ($(this).text().indexOf(filter_value) === -1) {
+ // hide
+ element.addClass("hidden");
+ hidden++;
+ }
+ else {
+ // show
+ element.removeClass("hidden");
+ shown++;
+ }
+ });
+
+ // Show placeholder if no rows will be displayed.
+ if (no_rows.length > 0) {
+ if (shown === 0) {
+ // Show placeholder, hide table.
+ no_rows.show();
+ table.hide();
+ }
+ else {
+ // Hide placeholder, show table.
+ no_rows.hide();
+ table.show();
+ }
+ }
+
+ // Manage dynamic header:
+ if (hidden > 0) {
+ // Calculate new dynamic sum values based on visible rows.
+ for (var column = 2; column < 20; column++) {
+ // Calculate summed value.
+ var cells = table_rows.find('td:nth-child(' + column + ')');
+ if (!cells.length) {
+ // No more columns...!
+ break;
+ }
+
+ var sum = 0, numer = 0, denom = 0;
+ $.each(cells.filter(':visible'), function () {
+ var ratio = $(this).data("ratio");
+ if (ratio) {
+ var splitted = ratio.split(" ");
+ numer += parseInt(splitted[0], 10);
+ denom += parseInt(splitted[1], 10);
+ }
+ else {
+ sum += parseInt(this.innerHTML, 10);
+ }
+ });
+
+ // Get footer cell element.
+ var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')');
+
+ // Set value into dynamic footer cell element.
+ if (cells[0].innerHTML.indexOf('%') > -1) {
+ // Percentage columns use the numerator and denominator,
+ // and adapt to the number of decimal places.
+ var match = /\.([0-9]+)/.exec(cells[0].innerHTML);
+ var places = 0;
+ if (match) {
+ places = match[1].length;
+ }
+ var pct = numer * 100 / denom;
+ footer_cell.text(pct.toFixed(places) + '%');
+ }
+ else {
+ footer_cell.text(sum);
+ }
+ }
+
+ // Hide standard footer, show dynamic footer.
+ table_footer.addClass("hidden");
+ table_dynamic_footer.removeClass("hidden");
+ }
+ else {
+ // Show standard footer, hide dynamic footer.
+ table_footer.removeClass("hidden");
+ table_dynamic_footer.addClass("hidden");
+ }
+ }
+ }));
+
+ // Trigger change event on setup, to force filter on page refresh
+ // (filter value may still be present).
+ $("#filter").trigger("change");
+};
+
+// Loaded on index.html
+coverage.index_ready = function ($) {
+ // Look for a localStorage item containing previous sort settings:
+ var sort_list = [];
+ var storage_name = "COVERAGE_INDEX_SORT";
+ var stored_list = undefined;
+ try {
+ stored_list = localStorage.getItem(storage_name);
+ } catch(err) {}
+
+ if (stored_list) {
+ sort_list = JSON.parse('[[' + stored_list + ']]');
+ }
+
+ // Create a new widget which exists only to save and restore
+ // the sort order:
+ $.tablesorter.addWidget({
+ id: "persistentSort",
+
+ // Format is called by the widget before displaying:
+ format: function (table) {
+ if (table.config.sortList.length === 0 && sort_list.length > 0) {
+ // This table hasn't been sorted before - we'll use
+ // our stored settings:
+ $(table).trigger('sorton', [sort_list]);
+ }
+ else {
+ // This is not the first load - something has
+ // already defined sorting so we'll just update
+ // our stored value to match:
+ sort_list = table.config.sortList;
+ }
+ }
+ });
+
+ // Configure our tablesorter to handle the variable number of
+ // columns produced depending on report options:
+ var headers = [];
+ var col_count = $("table.index > thead > tr > th").length;
+
+ headers[0] = { sorter: 'text' };
+ for (i = 1; i < col_count-1; i++) {
+ headers[i] = { sorter: 'digit' };
+ }
+ headers[col_count-1] = { sorter: 'percent' };
+
+ // Enable the table sorter:
+ $("table.index").tablesorter({
+ widgets: ['persistentSort'],
+ headers: headers
+ });
+
+ coverage.assign_shortkeys();
+ coverage.wire_up_help_panel();
+ coverage.wire_up_filter();
+
+ // Watch for page unload events so we can save the final sort settings:
+ $(window).unload(function () {
+ try {
+ localStorage.setItem(storage_name, sort_list.toString())
+ } catch(err) {}
+ });
+};
+
+// -- pyfile stuff --
+
+coverage.pyfile_ready = function ($) {
+ // If we're directed to a particular line number, highlight the line.
+ var frag = location.hash;
+ if (frag.length > 2 && frag[1] === 't') {
+ $(frag).addClass('highlight');
+ coverage.set_sel(parseInt(frag.substr(2), 10));
+ }
+ else {
+ coverage.set_sel(0);
+ }
+
+ $(document)
+ .bind('keydown', 'j', coverage.to_next_chunk_nicely)
+ .bind('keydown', 'k', coverage.to_prev_chunk_nicely)
+ .bind('keydown', '0', coverage.to_top)
+ .bind('keydown', '1', coverage.to_first_chunk)
+ ;
+
+ $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");});
+ $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");});
+ $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
+ $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
+
+ coverage.assign_shortkeys();
+ coverage.wire_up_help_panel();
+
+ coverage.init_scroll_markers();
+
+ // Rebuild scroll markers when the window height changes.
+ $(window).resize(coverage.build_scroll_markers);
+};
+
+coverage.toggle_lines = function (btn, cls) {
+ btn = $(btn);
+ var show = "show_"+cls;
+ if (btn.hasClass(show)) {
+ $("#source ." + cls).removeClass(show);
+ btn.removeClass(show);
+ }
+ else {
+ $("#source ." + cls).addClass(show);
+ btn.addClass(show);
+ }
+ coverage.build_scroll_markers();
+};
+
+// Return the nth line div.
+coverage.line_elt = function (n) {
+ return $("#t" + n);
+};
+
+// Return the nth line number div.
+coverage.num_elt = function (n) {
+ return $("#n" + n);
+};
+
+// Set the selection. b and e are line numbers.
+coverage.set_sel = function (b, e) {
+ // The first line selected.
+ coverage.sel_begin = b;
+ // The next line not selected.
+ coverage.sel_end = (e === undefined) ? b+1 : e;
+};
+
+coverage.to_top = function () {
+ coverage.set_sel(0, 1);
+ coverage.scroll_window(0);
+};
+
+coverage.to_first_chunk = function () {
+ coverage.set_sel(0, 1);
+ coverage.to_next_chunk();
+};
+
+// Return a string indicating what kind of chunk this line belongs to,
+// or null if not a chunk.
+coverage.chunk_indicator = function (line_elt) {
+ var klass = line_elt.attr('class');
+ if (klass) {
+ var m = klass.match(/\bshow_\w+\b/);
+ if (m) {
+ return m[0];
+ }
+ }
+ return null;
+};
+
+coverage.to_next_chunk = function () {
+ var c = coverage;
+
+ // Find the start of the next colored chunk.
+ var probe = c.sel_end;
+ var chunk_indicator, probe_line;
+ while (true) {
+ probe_line = c.line_elt(probe);
+ if (probe_line.length === 0) {
+ return;
+ }
+ chunk_indicator = c.chunk_indicator(probe_line);
+ if (chunk_indicator) {
+ break;
+ }
+ probe++;
+ }
+
+ // There's a next chunk, `probe` points to it.
+ var begin = probe;
+
+ // Find the end of this chunk.
+ var next_indicator = chunk_indicator;
+ while (next_indicator === chunk_indicator) {
+ probe++;
+ probe_line = c.line_elt(probe);
+ next_indicator = c.chunk_indicator(probe_line);
+ }
+ c.set_sel(begin, probe);
+ c.show_selection();
+};
+
+coverage.to_prev_chunk = function () {
+ var c = coverage;
+
+ // Find the end of the prev colored chunk.
+ var probe = c.sel_begin-1;
+ var probe_line = c.line_elt(probe);
+ if (probe_line.length === 0) {
+ return;
+ }
+ var chunk_indicator = c.chunk_indicator(probe_line);
+ while (probe > 0 && !chunk_indicator) {
+ probe--;
+ probe_line = c.line_elt(probe);
+ if (probe_line.length === 0) {
+ return;
+ }
+ chunk_indicator = c.chunk_indicator(probe_line);
+ }
+
+ // There's a prev chunk, `probe` points to its last line.
+ var end = probe+1;
+
+ // Find the beginning of this chunk.
+ var prev_indicator = chunk_indicator;
+ while (prev_indicator === chunk_indicator) {
+ probe--;
+ probe_line = c.line_elt(probe);
+ prev_indicator = c.chunk_indicator(probe_line);
+ }
+ c.set_sel(probe+1, end);
+ c.show_selection();
+};
+
+// Return the line number of the line nearest pixel position pos
+coverage.line_at_pos = function (pos) {
+ var l1 = coverage.line_elt(1),
+ l2 = coverage.line_elt(2),
+ result;
+ if (l1.length && l2.length) {
+ var l1_top = l1.offset().top,
+ line_height = l2.offset().top - l1_top,
+ nlines = (pos - l1_top) / line_height;
+ if (nlines < 1) {
+ result = 1;
+ }
+ else {
+ result = Math.ceil(nlines);
+ }
+ }
+ else {
+ result = 1;
+ }
+ return result;
+};
+
+// Returns 0, 1, or 2: how many of the two ends of the selection are on
+// the screen right now?
+coverage.selection_ends_on_screen = function () {
+ if (coverage.sel_begin === 0) {
+ return 0;
+ }
+
+ var top = coverage.line_elt(coverage.sel_begin);
+ var next = coverage.line_elt(coverage.sel_end-1);
+
+ return (
+ (top.isOnScreen() ? 1 : 0) +
+ (next.isOnScreen() ? 1 : 0)
+ );
+};
+
+coverage.to_next_chunk_nicely = function () {
+ coverage.finish_scrolling();
+ if (coverage.selection_ends_on_screen() === 0) {
+ // The selection is entirely off the screen: select the top line on
+ // the screen.
+ var win = $(window);
+ coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop()));
+ }
+ coverage.to_next_chunk();
+};
+
+coverage.to_prev_chunk_nicely = function () {
+ coverage.finish_scrolling();
+ if (coverage.selection_ends_on_screen() === 0) {
+ var win = $(window);
+ coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height()));
+ }
+ coverage.to_prev_chunk();
+};
+
+// Select line number lineno, or if it is in a colored chunk, select the
+// entire chunk
+coverage.select_line_or_chunk = function (lineno) {
+ var c = coverage;
+ var probe_line = c.line_elt(lineno);
+ if (probe_line.length === 0) {
+ return;
+ }
+ var the_indicator = c.chunk_indicator(probe_line);
+ if (the_indicator) {
+ // The line is in a highlighted chunk.
+ // Search backward for the first line.
+ var probe = lineno;
+ var indicator = the_indicator;
+ while (probe > 0 && indicator === the_indicator) {
+ probe--;
+ probe_line = c.line_elt(probe);
+ if (probe_line.length === 0) {
+ break;
+ }
+ indicator = c.chunk_indicator(probe_line);
+ }
+ var begin = probe + 1;
+
+ // Search forward for the last line.
+ probe = lineno;
+ indicator = the_indicator;
+ while (indicator === the_indicator) {
+ probe++;
+ probe_line = c.line_elt(probe);
+ indicator = c.chunk_indicator(probe_line);
+ }
+
+ coverage.set_sel(begin, probe);
+ }
+ else {
+ coverage.set_sel(lineno);
+ }
+};
+
+coverage.show_selection = function () {
+ var c = coverage;
+
+ // Highlight the lines in the chunk
+ $(".linenos .highlight").removeClass("highlight");
+ for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) {
+ c.num_elt(probe).addClass("highlight");
+ }
+
+ c.scroll_to_selection();
+};
+
+coverage.scroll_to_selection = function () {
+ // Scroll the page if the chunk isn't fully visible.
+ if (coverage.selection_ends_on_screen() < 2) {
+ // Need to move the page. The html,body trick makes it scroll in all
+ // browsers, got it from http://stackoverflow.com/questions/3042651
+ var top = coverage.line_elt(coverage.sel_begin);
+ var top_pos = parseInt(top.offset().top, 10);
+ coverage.scroll_window(top_pos - 30);
+ }
+};
+
+coverage.scroll_window = function (to_pos) {
+ $("html,body").animate({scrollTop: to_pos}, 200);
+};
+
+coverage.finish_scrolling = function () {
+ $("html,body").stop(true, true);
+};
+
+coverage.init_scroll_markers = function () {
+ var c = coverage;
+ // Init some variables
+ c.lines_len = $('#source p').length;
+ c.body_h = $('body').height();
+ c.header_h = $('div#header').height();
+
+ // Build html
+ c.build_scroll_markers();
+};
+
+coverage.build_scroll_markers = function () {
+ var c = coverage,
+ min_line_height = 3,
+ max_line_height = 10,
+ visible_window_h = $(window).height();
+
+ c.lines_to_mark = $('#source').find('p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par');
+ $('#scroll_marker').remove();
+ // Don't build markers if the window has no scroll bar.
+ if (c.body_h <= visible_window_h) {
+ return;
+ }
+
+ $("body").append("<div id='scroll_marker'>&nbsp;</div>");
+ var scroll_marker = $('#scroll_marker'),
+ marker_scale = scroll_marker.height() / c.body_h,
+ line_height = scroll_marker.height() / c.lines_len;
+
+ // Line height must be between the extremes.
+ if (line_height > min_line_height) {
+ if (line_height > max_line_height) {
+ line_height = max_line_height;
+ }
+ }
+ else {
+ line_height = min_line_height;
+ }
+
+ var previous_line = -99,
+ last_mark,
+ last_top,
+ offsets = {};
+
+ // Calculate line offsets outside loop to prevent relayouts
+ c.lines_to_mark.each(function() {
+ offsets[this.id] = $(this).offset().top;
+ });
+ c.lines_to_mark.each(function () {
+ var id_name = $(this).attr('id'),
+ line_top = Math.round(offsets[id_name] * marker_scale),
+ line_number = parseInt(id_name.substring(1, id_name.length));
+
+ if (line_number === previous_line + 1) {
+ // If this solid missed block just make previous mark higher.
+ last_mark.css({
+ 'height': line_top + line_height - last_top
+ });
+ }
+ else {
+ // Add colored line in scroll_marker block.
+ scroll_marker.append('<div id="m' + line_number + '" class="marker"></div>');
+ last_mark = $('#m' + line_number);
+ last_mark.css({
+ 'height': line_height,
+ 'top': line_top
+ });
+ last_top = line_top;
+ }
+
+ previous_line = line_number;
+ });
+};
diff --git a/third_party/python/coverage/coverage/htmlfiles/index.html b/third_party/python/coverage/coverage/htmlfiles/index.html
new file mode 100644
index 0000000000..4129bc31b9
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/index.html
@@ -0,0 +1,118 @@
+{# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
+{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
+
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>{{ title|escape }}</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ {% if extra_css %}
+ <link rel="stylesheet" href="{{ extra_css }}" type="text/css">
+ {% endif %}
+ <script type="text/javascript" src="jquery.min.js"></script>
+ <script type="text/javascript" src="jquery.ba-throttle-debounce.min.js"></script>
+ <script type="text/javascript" src="jquery.tablesorter.min.js"></script>
+ <script type="text/javascript" src="jquery.hotkeys.js"></script>
+ <script type="text/javascript" src="coverage_html.js"></script>
+ <script type="text/javascript">
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body class="indexfile">
+
+<div id="header">
+ <div class="content">
+ <h1>{{ title|escape }}:
+ <span class="pc_cov">{{totals.pc_covered_str}}%</span>
+ </h1>
+
+ <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" />
+
+ <form id="filter_container">
+ <input id="filter" type="text" value="" placeholder="filter..." />
+ </form>
+ </div>
+</div>
+
+<div class="help_panel">
+ <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" />
+ <p class="legend">Hot-keys on this page</p>
+ <div>
+ <p class="keyhelp">
+ <span class="key">n</span>
+ <span class="key">s</span>
+ <span class="key">m</span>
+ <span class="key">x</span>
+ {% if has_arcs %}
+ <span class="key">b</span>
+ <span class="key">p</span>
+ {% endif %}
+ <span class="key">c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id="index">
+ <table class="index">
+ <thead>
+ {# The title="" attr doesn"t work in Safari. #}
+ <tr class="tablehead" title="Click to sort">
+ <th class="name left headerSortDown shortkey_n">Module</th>
+ <th class="shortkey_s">statements</th>
+ <th class="shortkey_m">missing</th>
+ <th class="shortkey_x">excluded</th>
+ {% if has_arcs %}
+ <th class="shortkey_b">branches</th>
+ <th class="shortkey_p">partial</th>
+ {% endif %}
+ <th class="right shortkey_c">coverage</th>
+ </tr>
+ </thead>
+ {# HTML syntax requires thead, tfoot, tbody #}
+ <tfoot>
+ <tr class="total">
+ <td class="name left">Total</td>
+ <td>{{totals.n_statements}}</td>
+ <td>{{totals.n_missing}}</td>
+ <td>{{totals.n_excluded}}</td>
+ {% if has_arcs %}
+ <td>{{totals.n_branches}}</td>
+ <td>{{totals.n_partial_branches}}</td>
+ {% endif %}
+ <td class="right" data-ratio="{{totals.ratio_covered|pair}}">{{totals.pc_covered_str}}%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+ {% for file in files %}
+ <tr class="file">
+ <td class="name left"><a href="{{file.html_filename}}">{{file.relative_filename}}</a></td>
+ <td>{{file.nums.n_statements}}</td>
+ <td>{{file.nums.n_missing}}</td>
+ <td>{{file.nums.n_excluded}}</td>
+ {% if has_arcs %}
+ <td>{{file.nums.n_branches}}</td>
+ <td>{{file.nums.n_partial_branches}}</td>
+ {% endif %}
+ <td class="right" data-ratio="{{file.nums.ratio_covered|pair}}">{{file.nums.pc_covered_str}}%</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+
+ <p id="no_rows">
+ No items found using the specified filter.
+ </p>
+</div>
+
+<div id="footer">
+ <div class="content">
+ <p>
+ <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
+ created at {{ time_stamp }}
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/third_party/python/coverage/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js b/third_party/python/coverage/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js
new file mode 100644
index 0000000000..648fe5d3c2
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery throttle / debounce - v1.1 - 3/7/2010
+ * http://benalman.com/projects/jquery-throttle-debounce-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this);
diff --git a/third_party/python/coverage/coverage/htmlfiles/jquery.hotkeys.js b/third_party/python/coverage/coverage/htmlfiles/jquery.hotkeys.js
new file mode 100644
index 0000000000..09b21e03c7
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/jquery.hotkeys.js
@@ -0,0 +1,99 @@
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
+
+(function(jQuery){
+
+ jQuery.hotkeys = {
+ version: "0.8",
+
+ specialKeys: {
+ 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
+ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+ 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+ },
+
+ shiftNums: {
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+ ".": ">", "/": "?", "\\": "|"
+ }
+ };
+
+ function keyHandler( handleObj ) {
+ // Only care when a possible input has been specified
+ if ( typeof handleObj.data !== "string" ) {
+ return;
+ }
+
+ var origHandler = handleObj.handler,
+ keys = handleObj.data.toLowerCase().split(" ");
+
+ handleObj.handler = function( event ) {
+ // Don't fire in text-accepting inputs that we didn't directly bind to
+ if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+ event.target.type === "text") ) {
+ return;
+ }
+
+ // Keypress represents characters, not special keys
+ var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+ character = String.fromCharCode( event.which ).toLowerCase(),
+ key, modif = "", possible = {};
+
+ // check combinations (alt|ctrl|shift+anything)
+ if ( event.altKey && special !== "alt" ) {
+ modif += "alt+";
+ }
+
+ if ( event.ctrlKey && special !== "ctrl" ) {
+ modif += "ctrl+";
+ }
+
+ // TODO: Need to make sure this works consistently across platforms
+ if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+ modif += "meta+";
+ }
+
+ if ( event.shiftKey && special !== "shift" ) {
+ modif += "shift+";
+ }
+
+ if ( special ) {
+ possible[ modif + special ] = true;
+
+ } else {
+ possible[ modif + character ] = true;
+ possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+ if ( modif === "shift+" ) {
+ possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+ }
+ }
+
+ for ( var i = 0, l = keys.length; i < l; i++ ) {
+ if ( possible[ keys[i] ] ) {
+ return origHandler.apply( this, arguments );
+ }
+ }
+ };
+ }
+
+ jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+ jQuery.event.special[ this ] = { add: keyHandler };
+ });
+
+})( jQuery );
diff --git a/third_party/python/coverage/coverage/htmlfiles/jquery.isonscreen.js b/third_party/python/coverage/coverage/htmlfiles/jquery.isonscreen.js
new file mode 100644
index 0000000000..0182ebd213
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/jquery.isonscreen.js
@@ -0,0 +1,53 @@
+/* Copyright (c) 2010
+ * @author Laurence Wheway
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * @version 1.2.0
+ */
+(function($) {
+ jQuery.extend({
+ isOnScreen: function(box, container) {
+ //ensure numbers come in as intgers (not strings) and remove 'px' is it's there
+ for(var i in box){box[i] = parseFloat(box[i])};
+ for(var i in container){container[i] = parseFloat(container[i])};
+
+ if(!container){
+ container = {
+ left: $(window).scrollLeft(),
+ top: $(window).scrollTop(),
+ width: $(window).width(),
+ height: $(window).height()
+ }
+ }
+
+ if( box.left+box.width-container.left > 0 &&
+ box.left < container.width+container.left &&
+ box.top+box.height-container.top > 0 &&
+ box.top < container.height+container.top
+ ) return true;
+ return false;
+ }
+ })
+
+
+ jQuery.fn.isOnScreen = function (container) {
+ for(var i in container){container[i] = parseFloat(container[i])};
+
+ if(!container){
+ container = {
+ left: $(window).scrollLeft(),
+ top: $(window).scrollTop(),
+ width: $(window).width(),
+ height: $(window).height()
+ }
+ }
+
+ if( $(this).offset().left+$(this).width()-container.left > 0 &&
+ $(this).offset().left < container.width+container.left &&
+ $(this).offset().top+$(this).height()-container.top > 0 &&
+ $(this).offset().top < container.height+container.top
+ ) return true;
+ return false;
+ }
+})(jQuery);
diff --git a/third_party/python/coverage/coverage/htmlfiles/jquery.min.js b/third_party/python/coverage/coverage/htmlfiles/jquery.min.js
new file mode 100644
index 0000000000..d1608e37ff
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;
+if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")
+},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.ActiveXObject&&m(a).on("unload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m}); \ No newline at end of file
diff --git a/third_party/python/coverage/coverage/htmlfiles/jquery.tablesorter.min.js b/third_party/python/coverage/coverage/htmlfiles/jquery.tablesorter.min.js
new file mode 100644
index 0000000000..64c7007129
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/jquery.tablesorter.min.js
@@ -0,0 +1,2 @@
+
+(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,cells[i]);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,node){var l=parsers.length;for(var i=1;i<l;i++){if(parsers[i].is($.trim(getElementText(table.config,node)),table,node)){return parsers[i];}}return parsers[0];}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=table.tBodies[0].rows[i],cols=[];cache.row.push($(c));for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c.cells[j]),table,c.cells[j]));}cols.push(i);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){if(!node)return"";var t="";if(config.textExtraction=="simple"){if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){t=node.childNodes[0].innerHTML;}else{t=node.innerHTML;}}else{if(typeof(config.textExtraction)=="function"){t=config.textExtraction(node);}else{t=$(node).text();}}return t;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){rows.push(r[n[i][checkCell]]);if(!table.config.appender){var o=r[n[i][checkCell]];var l=o.length;for(var j=0;j<l;j++){tableBody[0].appendChild(o[j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false,tableHeadersRows=[];for(var i=0;i<table.tHead.rows.length;i++){tableHeadersRows[i]=0;};$tableHeaders=$("thead th",table);$tableHeaders.each(function(index){this.count=0;this.column=index;this.order=formatSortingOrder(table.config.sortInitialOrder);if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(!this.sortDisabled){$(this).addClass(table.config.cssHeader);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){i=(v.toLowerCase()=="desc")?1:0;}else{i=(v==(0||1))?v:0;}return i;}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(getCachedSortType(table.config.parsers,c)=="text")?((order==0)?"sortText":"sortTextDesc"):((order==0)?"sortNumeric":"sortNumericDesc");var e="e"+i;dynamicExp+="var "+e+" = "+s+"(a["+c+"],b["+c+"]); ";dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function sortText(a,b){return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){var DECIMAL='\\'+config.decimal;var exp='/(^[+]?0('+DECIMAL+'0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)'+DECIMAL+'(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*'+DECIMAL+'0+$)/';return RegExp(exp).test($.trim(s));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}$("tr:visible",table.tBodies[0]).filter(':even').removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0]).end().filter(':odd').removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]);if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery); \ No newline at end of file
diff --git a/third_party/python/coverage/coverage/htmlfiles/keybd_closed.png b/third_party/python/coverage/coverage/htmlfiles/keybd_closed.png
new file mode 100644
index 0000000000..db114023f0
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/keybd_closed.png
Binary files differ
diff --git a/third_party/python/coverage/coverage/htmlfiles/keybd_open.png b/third_party/python/coverage/coverage/htmlfiles/keybd_open.png
new file mode 100644
index 0000000000..db114023f0
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/keybd_open.png
Binary files differ
diff --git a/third_party/python/coverage/coverage/htmlfiles/pyfile.html b/third_party/python/coverage/coverage/htmlfiles/pyfile.html
new file mode 100644
index 0000000000..eb0f99c812
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/pyfile.html
@@ -0,0 +1,112 @@
+{# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
+{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
+
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ {# IE8 rounds line-height incorrectly, and adding this emulateIE7 line makes it right! #}
+ {# http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/7684445e-f080-4d8f-8529-132763348e21 #}
+ <meta http-equiv="X-UA-Compatible" content="IE=emulateIE7" />
+ <title>Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}%</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ {% if extra_css %}
+ <link rel="stylesheet" href="{{ extra_css }}" type="text/css">
+ {% endif %}
+ <script type="text/javascript" src="jquery.min.js"></script>
+ <script type="text/javascript" src="jquery.hotkeys.js"></script>
+ <script type="text/javascript" src="jquery.isonscreen.js"></script>
+ <script type="text/javascript" src="coverage_html.js"></script>
+ <script type="text/javascript">
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body class="pyfile">
+
+<div id="header">
+ <div class="content">
+ <h1>Coverage for <b>{{relative_filename|escape}}</b> :
+ <span class="pc_cov">{{nums.pc_covered_str}}%</span>
+ </h1>
+
+ <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" />
+
+ <h2 class="stats">
+ {{nums.n_statements}} statements &nbsp;
+ <span class="{{category.run}} shortkey_r button_toggle_run">{{nums.n_executed}} run</span>
+ <span class="{{category.mis}} shortkey_m button_toggle_mis">{{nums.n_missing}} missing</span>
+ <span class="{{category.exc}} shortkey_x button_toggle_exc">{{nums.n_excluded}} excluded</span>
+
+ {% if has_arcs %}
+ <span class="{{category.par}} shortkey_p button_toggle_par">{{nums.n_partial_branches}} partial</span>
+ {% endif %}
+ </h2>
+ </div>
+</div>
+
+<div class="help_panel">
+ <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" />
+ <p class="legend">Hot-keys on this page</p>
+ <div>
+ <p class="keyhelp">
+ <span class="key">r</span>
+ <span class="key">m</span>
+ <span class="key">x</span>
+ <span class="key">p</span> &nbsp; toggle line displays
+ </p>
+ <p class="keyhelp">
+ <span class="key">j</span>
+ <span class="key">k</span> &nbsp; next/prev highlighted chunk
+ </p>
+ <p class="keyhelp">
+ <span class="key">0</span> &nbsp; (zero) top of page
+ </p>
+ <p class="keyhelp">
+ <span class="key">1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id="source">
+ {% for line in lines -%}
+ {% joined %}
+ <p id="t{{line.number}}" class="{{line.css_class}}">
+ <span class="n"><a href="#t{{line.number}}">{{line.number}}</a></span>
+ <span class="t">{{line.html}}&nbsp;</span>
+ {% if line.context_list %}
+ <input type="checkbox" id="ctxs{{line.number}}" />
+ {% endif %}
+ {# Things that should float right in the line. #}
+ <span class="r">
+ {% if line.annotate %}
+ <span class="annotate short">{{line.annotate}}</span>
+ <span class="annotate long">{{line.annotate_long}}</span>
+ {% endif %}
+ {% if line.contexts %}
+ <label for="ctxs{{line.number}}" class="ctx">{{ line.contexts_label }}</label>
+ {% endif %}
+ </span>
+ {# Things that should appear below the line. #}
+ {% if line.context_list %}
+ <span class="ctxs">
+ {% for context in line.context_list %}
+ <span>{{context}}</span>
+ {% endfor %}
+ </span>
+ {% endif %}
+ </p>
+ {% endjoined %}
+ {% endfor %}
+</div>
+
+<div id="footer">
+ <div class="content">
+ <p>
+ <a class="nav" href="index.html">&#xab; index</a> &nbsp; &nbsp; <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
+ created at {{ time_stamp }}
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/third_party/python/coverage/coverage/htmlfiles/style.css b/third_party/python/coverage/coverage/htmlfiles/style.css
new file mode 100644
index 0000000000..e8ff57657f
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/style.css
@@ -0,0 +1,124 @@
+@charset "UTF-8";
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+/* Don't edit this .css file. Edit the .scss file instead! */
+html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
+
+body { font-family: georgia, serif; font-size: 1em; }
+
+html > body { font-size: 16px; }
+
+p { font-size: .75em; line-height: 1.33333333em; }
+
+table { border-collapse: collapse; }
+
+td { vertical-align: top; }
+
+table tr.hidden { display: none !important; }
+
+p#no_rows { display: none; font-size: 1.2em; }
+
+a.nav { text-decoration: none; color: inherit; }
+a.nav:hover { text-decoration: underline; color: inherit; }
+
+#header { background: #f8f8f8; width: 100%; border-bottom: 1px solid #eee; }
+
+.indexfile #footer { margin: 1em 3em; }
+
+.pyfile #footer { margin: 1em 1em; }
+
+#footer .content { padding: 0; font-size: 85%; font-family: verdana, sans-serif; color: #666666; font-style: italic; }
+
+#index { margin: 1em 0 0 3em; }
+
+#header .content { padding: 1em 3rem; }
+
+h1 { font-size: 1.25em; display: inline-block; }
+
+#filter_container { display: inline-block; float: right; margin: 0 2em 0 0; }
+#filter_container input { width: 10em; }
+
+h2.stats { margin-top: .5em; font-size: 1em; }
+
+.stats span { border: 1px solid; border-radius: .1em; padding: .1em .5em; margin: 0 .1em; cursor: pointer; border-color: #ccc #999 #999 #ccc; }
+.stats span.run { background: #eeffee; }
+.stats span.run.show_run { border-color: #999 #ccc #ccc #999; background: #ddffdd; }
+.stats span.mis { background: #ffeeee; }
+.stats span.mis.show_mis { border-color: #999 #ccc #ccc #999; background: #ffdddd; }
+.stats span.exc { background: #f7f7f7; }
+.stats span.exc.show_exc { border-color: #999 #ccc #ccc #999; background: #eeeeee; }
+.stats span.par { background: #ffffd5; }
+.stats span.par.show_par { border-color: #999 #ccc #ccc #999; background: #ffffaa; }
+
+#source p .annotate.long, .help_panel { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; box-shadow: #cccccc .2em .2em .2em; color: #333; padding: .25em .5em; }
+
+#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
+
+#keyboard_icon { float: right; margin: 5px; cursor: pointer; }
+
+.help_panel { padding: .5em; border: 1px solid #883; }
+.help_panel .legend { font-style: italic; margin-bottom: 1em; }
+.indexfile .help_panel { width: 20em; height: 4em; }
+.pyfile .help_panel { width: 16em; height: 8em; }
+
+#panel_icon { float: right; cursor: pointer; }
+
+.keyhelp { margin: .75em; }
+.keyhelp .key { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: monospace; font-weight: bold; background: #eee; }
+
+#source { padding: 1em 0 1em 3rem; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
+#source p { position: relative; white-space: pre; }
+#source p * { box-sizing: border-box; }
+#source p .n { float: left; text-align: right; width: 3rem; box-sizing: border-box; margin-left: -3rem; padding-right: 1em; color: #999999; font-family: verdana, sans-serif; }
+#source p .n a { text-decoration: none; color: #999999; font-size: .8333em; line-height: 1em; }
+#source p .n a:hover { text-decoration: underline; color: #999999; }
+#source p.highlight .n { background: #ffdd00; }
+#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid white; }
+#source p .t:hover { background: #f2f2f2; }
+#source p .t:hover ~ .r .annotate.long { display: block; }
+#source p .t .com { color: green; font-style: italic; line-height: 1px; }
+#source p .t .key { font-weight: bold; line-height: 1px; }
+#source p .t .str { color: #000080; }
+#source p.mis .t { border-left: 0.2em solid #ff0000; }
+#source p.mis.show_mis .t { background: #ffdddd; }
+#source p.mis.show_mis .t:hover { background: #f2d2d2; }
+#source p.run .t { border-left: 0.2em solid #00ff00; }
+#source p.run.show_run .t { background: #ddffdd; }
+#source p.run.show_run .t:hover { background: #d2f2d2; }
+#source p.exc .t { border-left: 0.2em solid #808080; }
+#source p.exc.show_exc .t { background: #eeeeee; }
+#source p.exc.show_exc .t:hover { background: #e2e2e2; }
+#source p.par .t { border-left: 0.2em solid #eeee99; }
+#source p.par.show_par .t { background: #ffffaa; }
+#source p.par.show_par .t:hover { background: #f2f2a2; }
+#source p .r { position: absolute; top: 0; right: 2.5em; font-family: verdana, sans-serif; }
+#source p .annotate { font-family: georgia; color: #666; padding-right: .5em; }
+#source p .annotate.short:hover ~ .long { display: block; }
+#source p .annotate.long { width: 30em; right: 2.5em; }
+#source p input { display: none; }
+#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; }
+#source p input ~ .r label.ctx::before { content: "▶ "; }
+#source p input ~ .r label.ctx:hover { background: #d5f7ff; color: #666; }
+#source p input:checked ~ .r label.ctx { background: #aaeeff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }
+#source p input:checked ~ .r label.ctx::before { content: "▼ "; }
+#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; }
+#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; }
+#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: verdana, sans-serif; white-space: nowrap; background: #aaeeff; border-radius: .25em; margin-right: 1.75em; }
+#source p .ctxs span { display: block; text-align: right; }
+
+#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; }
+#index td.left, #index th.left { padding-left: 0; }
+#index td.right, #index th.right { padding-right: 0; }
+#index td.name, #index th.name { text-align: left; width: auto; }
+#index th { font-style: italic; color: #333; border-bottom: 1px solid #ccc; cursor: pointer; }
+#index th:hover { background: #eee; border-bottom: 1px solid #999; }
+#index th.headerSortDown, #index th.headerSortUp { border-bottom: 1px solid #000; white-space: nowrap; background: #eee; }
+#index th.headerSortDown:after { content: " ↓"; }
+#index th.headerSortUp:after { content: " ↑"; }
+#index td.name a { text-decoration: none; color: #000; }
+#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; }
+#index tr.file:hover { background: #eeeeee; }
+#index tr.file:hover td.name { text-decoration: underline; color: #000; }
+
+#scroll_marker { position: fixed; right: 0; top: 0; width: 16px; height: 100%; background: white; border-left: 1px solid #eee; will-change: transform; }
+#scroll_marker .marker { background: #ddd; position: absolute; min-height: 3px; width: 100%; }
diff --git a/third_party/python/coverage/coverage/htmlfiles/style.scss b/third_party/python/coverage/coverage/htmlfiles/style.scss
new file mode 100644
index 0000000000..901cccc4ed
--- /dev/null
+++ b/third_party/python/coverage/coverage/htmlfiles/style.scss
@@ -0,0 +1,537 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+// CSS styles for coverage.py HTML reports.
+
+// When you edit this file, you need to run "make css" to get the CSS file
+// generated, and then check in both the .scss and the .css files.
+
+// When working on the file, this command is useful:
+// sass --watch --style=compact --sourcemap=none --no-cache coverage/htmlfiles/style.scss:htmlcov/style.css
+
+// Ignore this comment, it's for the CSS output file:
+/* Don't edit this .css file. Edit the .scss file instead! */
+
+// Dimensions
+$left-gutter: 3rem;
+
+// Page-wide styles
+html, body, h1, h2, h3, p, table, td, th {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+
+// Set baseline grid to 16 pt.
+body {
+ font-family: georgia, serif;
+ font-size: 1em;
+}
+
+html>body {
+ font-size: 16px;
+}
+
+// Set base font size to 12/16
+p {
+ font-size: .75em; // 12/16
+ line-height: 1.33333333em; // 16/12
+}
+
+table {
+ border-collapse: collapse;
+}
+td {
+ vertical-align: top;
+}
+table tr.hidden {
+ display: none !important;
+}
+
+p#no_rows {
+ display: none;
+ font-size: 1.2em;
+}
+
+a.nav {
+ text-decoration: none;
+ color: inherit;
+
+ &:hover {
+ text-decoration: underline;
+ color: inherit;
+ }
+}
+
+// Page structure
+#header {
+ background: #f8f8f8;
+ width: 100%;
+ border-bottom: 1px solid #eee;
+}
+
+.indexfile #footer {
+ margin: 1em 3em;
+}
+
+.pyfile #footer {
+ margin: 1em 1em;
+}
+
+#footer .content {
+ padding: 0;
+ font-size: 85%;
+ font-family: verdana, sans-serif;
+ color: #666666;
+ font-style: italic;
+}
+
+#index {
+ margin: 1em 0 0 3em;
+}
+
+// Header styles
+#header .content {
+ padding: 1em $left-gutter;
+}
+
+h1 {
+ font-size: 1.25em;
+ display: inline-block;
+}
+
+#filter_container {
+ display: inline-block;
+ float: right;
+ margin: 0 2em 0 0;
+
+ input {
+ width: 10em;
+ }
+}
+
+$pln-color: #ffffff;
+$mis-color: #ffdddd;
+$run-color: #ddffdd;
+$exc-color: #eeeeee;
+$par-color: #ffffaa;
+
+$off-button-lighten: 50%;
+
+h2.stats {
+ margin-top: .5em;
+ font-size: 1em;
+}
+.stats span {
+ border: 1px solid;
+ border-radius: .1em;
+ padding: .1em .5em;
+ margin: 0 .1em;
+ cursor: pointer;
+ border-color: #ccc #999 #999 #ccc;
+
+ &.run {
+ background: mix($run-color, #fff, $off-button-lighten);
+ &.show_run {
+ border-color: #999 #ccc #ccc #999;
+ background: $run-color;
+ }
+ }
+ &.mis {
+ background: mix($mis-color, #fff, $off-button-lighten);
+ &.show_mis {
+ border-color: #999 #ccc #ccc #999;
+ background: $mis-color;
+ }
+ }
+ &.exc {
+ background: mix($exc-color, #fff, $off-button-lighten);
+ &.show_exc {
+ border-color: #999 #ccc #ccc #999;
+ background: $exc-color;
+ }
+ }
+ &.par {
+ background: mix($par-color, #fff, $off-button-lighten);
+ &.show_par {
+ border-color: #999 #ccc #ccc #999;
+ background: $par-color;
+ }
+ }
+}
+
+// Yellow post-it things.
+%popup {
+ display: none;
+ position: absolute;
+ z-index: 999;
+ background: #ffffcc;
+ border: 1px solid #888;
+ border-radius: .2em;
+ box-shadow: #cccccc .2em .2em .2em;
+ color: #333;
+ padding: .25em .5em;
+}
+
+// Yellow post-it's in the text listings.
+%in-text-popup {
+ @extend %popup;
+ white-space: normal;
+ float: right;
+ top: 1.75em;
+ right: 1em;
+ height: auto;
+}
+
+// Help panel
+#keyboard_icon {
+ float: right;
+ margin: 5px;
+ cursor: pointer;
+}
+
+.help_panel {
+ @extend %popup;
+ padding: .5em;
+ border: 1px solid #883;
+
+ .legend {
+ font-style: italic;
+ margin-bottom: 1em;
+ }
+
+ .indexfile & {
+ width: 20em;
+ height: 4em;
+ }
+
+ .pyfile & {
+ width: 16em;
+ height: 8em;
+ }
+}
+
+#panel_icon {
+ float: right;
+ cursor: pointer;
+}
+
+.keyhelp {
+ margin: .75em;
+
+ .key {
+ border: 1px solid black;
+ border-color: #888 #333 #333 #888;
+ padding: .1em .35em;
+ font-family: monospace;
+ font-weight: bold;
+ background: #eee;
+ }
+}
+
+// Source file styles
+
+$hover-dark-amt: 95%;
+$pln-hover-color: mix($pln-color, #000, $hover-dark-amt);
+$mis-hover-color: mix($mis-color, #000, $hover-dark-amt);
+$run-hover-color: mix($run-color, #000, $hover-dark-amt);
+$exc-hover-color: mix($exc-color, #000, $hover-dark-amt);
+$par-hover-color: mix($par-color, #000, $hover-dark-amt);
+
+// The slim bar at the left edge of the source lines, colored by coverage.
+$border-indicator-width: .2em;
+
+$context-panel-color: #aaeeff;
+
+#source {
+ padding: 1em 0 1em $left-gutter;
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+
+ p {
+ // position relative makes position:absolute pop-ups appear in the right place.
+ position: relative;
+ white-space: pre;
+
+ * {
+ box-sizing: border-box;
+ }
+
+ .n {
+ float: left;
+ text-align: right;
+ width: $left-gutter;
+ box-sizing: border-box;
+ margin-left: -$left-gutter;
+ padding-right: 1em;
+ color: #999999;
+ font-family: verdana, sans-serif;
+
+ a {
+ text-decoration: none;
+ color: #999999;
+ font-size: .8333em; // 10/12
+ line-height: 1em;
+ &:hover {
+ text-decoration: underline;
+ color: #999999;
+ }
+ }
+ }
+
+ &.highlight .n {
+ background: #ffdd00;
+ }
+
+ .t {
+ display: inline-block;
+ width: 100%;
+ box-sizing: border-box;
+ margin-left: -.5em;
+ padding-left: .5em - $border-indicator-width;
+ border-left: $border-indicator-width solid white;
+
+ &:hover {
+ background: $pln-hover-color;
+
+ & ~ .r .annotate.long {
+ display: block;
+ }
+ }
+
+ // Syntax coloring
+ .com {
+ color: green;
+ font-style: italic;
+ line-height: 1px;
+ }
+ .key {
+ font-weight: bold;
+ line-height: 1px;
+ }
+ .str {
+ color: #000080;
+ }
+ }
+
+ &.mis {
+ .t {
+ border-left: $border-indicator-width solid #ff0000;
+ }
+
+ &.show_mis .t {
+ background: $mis-color;
+
+ &:hover {
+ background: $mis-hover-color;
+ }
+ }
+ }
+
+ &.run {
+ .t {
+ border-left: $border-indicator-width solid #00ff00;
+ }
+
+ &.show_run .t {
+ background: $run-color;
+
+ &:hover {
+ background: $run-hover-color;
+ }
+ }
+ }
+
+ &.exc {
+ .t {
+ border-left: $border-indicator-width solid #808080;
+ }
+
+ &.show_exc .t {
+ background: $exc-color;
+
+ &:hover {
+ background: $exc-hover-color;
+ }
+ }
+ }
+
+ &.par {
+ .t {
+ border-left: $border-indicator-width solid #eeee99;
+ }
+
+ &.show_par .t {
+ background: $par-color;
+
+ &:hover {
+ background: $par-hover-color;
+ }
+ }
+
+ }
+
+ .r {
+ position: absolute;
+ top: 0;
+ right: 2.5em;
+ font-family: verdana, sans-serif;
+ }
+
+ .annotate {
+ font-family: georgia;
+ color: #666;
+ padding-right: .5em;
+
+ &.short:hover ~ .long {
+ display: block;
+ }
+
+ &.long {
+ @extend %in-text-popup;
+ width: 30em;
+ right: 2.5em;
+ }
+ }
+
+ input {
+ display: none;
+
+ & ~ .r label.ctx {
+ cursor: pointer;
+ border-radius: .25em;
+ &::before {
+ content: "▶ ";
+ }
+ &:hover {
+ background: mix($context-panel-color, #fff, 50%);
+ color: #666;
+ }
+ }
+
+ &:checked ~ .r label.ctx {
+ background: $context-panel-color;
+ color: #666;
+ border-radius: .75em .75em 0 0;
+ padding: 0 .5em;
+ margin: -.25em 0;
+ &::before {
+ content: "▼ ";
+ }
+ }
+
+ &:checked ~ .ctxs {
+ padding: .25em .5em;
+ overflow-y: scroll;
+ max-height: 10.5em;
+ }
+ }
+
+ label.ctx {
+ color: #999;
+ display: inline-block;
+ padding: 0 .5em;
+ font-size: .8333em; // 10/12
+ }
+
+ .ctxs {
+ display: block;
+ max-height: 0;
+ overflow-y: hidden;
+ transition: all .2s;
+ padding: 0 .5em;
+ font-family: verdana, sans-serif;
+ white-space: nowrap;
+ background: $context-panel-color;
+ border-radius: .25em;
+ margin-right: 1.75em;
+ span {
+ display: block;
+ text-align: right;
+ }
+ }
+ }
+}
+
+
+// index styles
+#index {
+ td, th {
+ text-align: right;
+ width: 5em;
+ padding: .25em .5em;
+ border-bottom: 1px solid #eee;
+ &.left {
+ padding-left: 0;
+ }
+ &.right {
+ padding-right: 0;
+ }
+ &.name {
+ text-align: left;
+ width: auto;
+ }
+ }
+ th {
+ font-style: italic;
+ color: #333;
+ border-bottom: 1px solid #ccc;
+ cursor: pointer;
+ &:hover {
+ background: #eee;
+ border-bottom: 1px solid #999;
+ }
+ &.headerSortDown, &.headerSortUp {
+ border-bottom: 1px solid #000;
+ white-space: nowrap;
+ background: #eee;
+ }
+ &.headerSortDown:after {
+ content: " ↓";
+ }
+ &.headerSortUp:after {
+ content: " ↑";
+ }
+ }
+ td.name a {
+ text-decoration: none;
+ color: #000;
+ }
+
+ tr.total td,
+ tr.total_dynamic td {
+ font-weight: bold;
+ border-top: 1px solid #ccc;
+ border-bottom: none;
+ }
+ tr.file:hover {
+ background: #eeeeee;
+ td.name {
+ text-decoration: underline;
+ color: #000;
+ }
+ }
+}
+
+// scroll marker styles
+#scroll_marker {
+ position: fixed;
+ right: 0;
+ top: 0;
+ width: 16px;
+ height: 100%;
+ background: white;
+ border-left: 1px solid #eee;
+ will-change: transform; // for faster scrolling of fixed element in Chrome
+
+ .marker {
+ background: #ddd;
+ position: absolute;
+ min-height: 3px;
+ width: 100%;
+ }
+}
diff --git a/third_party/python/coverage/coverage/inorout.py b/third_party/python/coverage/coverage/inorout.py
new file mode 100644
index 0000000000..d5e8b22692
--- /dev/null
+++ b/third_party/python/coverage/coverage/inorout.py
@@ -0,0 +1,469 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Determining whether files are being measured/reported or not."""
+
+# For finding the stdlib
+import atexit
+import inspect
+import itertools
+import os
+import platform
+import re
+import sys
+import traceback
+
+from coverage import env
+from coverage.backward import code_object
+from coverage.disposition import FileDisposition, disposition_init
+from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher
+from coverage.files import prep_patterns, find_python_files, canonical_filename
+from coverage.misc import CoverageException
+from coverage.python import source_for_file, source_for_morf
+
+
+# Pypy has some unusual stuff in the "stdlib". Consider those locations
+# when deciding where the stdlib is. These modules are not used for anything,
+# they are modules importable from the pypy lib directories, so that we can
+# find those directories.
+_structseq = _pypy_irc_topic = None
+if env.PYPY:
+ try:
+ import _structseq
+ except ImportError:
+ pass
+
+ try:
+ import _pypy_irc_topic
+ except ImportError:
+ pass
+
+
+def canonical_path(morf, directory=False):
+ """Return the canonical path of the module or file `morf`.
+
+ If the module is a package, then return its directory. If it is a
+ module, then return its file, unless `directory` is True, in which
+ case return its enclosing directory.
+
+ """
+ morf_path = canonical_filename(source_for_morf(morf))
+ if morf_path.endswith("__init__.py") or directory:
+ morf_path = os.path.split(morf_path)[0]
+ return morf_path
+
+
+def name_for_module(filename, frame):
+ """Get the name of the module for a filename and frame.
+
+ For configurability's sake, we allow __main__ modules to be matched by
+ their importable name.
+
+ If loaded via runpy (aka -m), we can usually recover the "original"
+ full dotted module name, otherwise, we resort to interpreting the
+ file name to get the module's name. In the case that the module name
+ can't be determined, None is returned.
+
+ """
+ module_globals = frame.f_globals if frame is not None else {}
+ if module_globals is None: # pragma: only ironpython
+ # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296
+ module_globals = {}
+
+ dunder_name = module_globals.get('__name__', None)
+
+ if isinstance(dunder_name, str) and dunder_name != '__main__':
+ # This is the usual case: an imported module.
+ return dunder_name
+
+ loader = module_globals.get('__loader__', None)
+ for attrname in ('fullname', 'name'): # attribute renamed in py3.2
+ if hasattr(loader, attrname):
+ fullname = getattr(loader, attrname)
+ else:
+ continue
+
+ if isinstance(fullname, str) and fullname != '__main__':
+ # Module loaded via: runpy -m
+ return fullname
+
+ # Script as first argument to Python command line.
+ inspectedname = inspect.getmodulename(filename)
+ if inspectedname is not None:
+ return inspectedname
+ else:
+ return dunder_name
+
+
+def module_is_namespace(mod):
+ """Is the module object `mod` a PEP420 namespace module?"""
+ return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None
+
+
+def module_has_file(mod):
+ """Does the module object `mod` have an existing __file__ ?"""
+ mod__file__ = getattr(mod, '__file__', None)
+ if mod__file__ is None:
+ return False
+ return os.path.exists(mod__file__)
+
+
+class InOrOut(object):
+ """Machinery for determining what files to measure."""
+
+ def __init__(self, warn):
+ self.warn = warn
+
+ # The matchers for should_trace.
+ self.source_match = None
+ self.source_pkgs_match = None
+ self.pylib_paths = self.cover_paths = None
+ self.pylib_match = self.cover_match = None
+ self.include_match = self.omit_match = None
+ self.plugins = []
+ self.disp_class = FileDisposition
+
+ # The source argument can be directories or package names.
+ self.source = []
+ self.source_pkgs = []
+ self.source_pkgs_unmatched = []
+ self.omit = self.include = None
+
+ def configure(self, config):
+ """Apply the configuration to get ready for decision-time."""
+ for src in config.source or []:
+ if os.path.isdir(src):
+ self.source.append(canonical_filename(src))
+ else:
+ self.source_pkgs.append(src)
+ self.source_pkgs_unmatched = self.source_pkgs[:]
+
+ self.omit = prep_patterns(config.run_omit)
+ self.include = prep_patterns(config.run_include)
+
+ # The directories for files considered "installed with the interpreter".
+ self.pylib_paths = set()
+ if not config.cover_pylib:
+ # Look at where some standard modules are located. That's the
+ # indication for "installed with the interpreter". In some
+ # environments (virtualenv, for example), these modules may be
+ # spread across a few locations. Look at all the candidate modules
+ # we've imported, and take all the different ones.
+ for m in (atexit, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback):
+ if m is not None and hasattr(m, "__file__"):
+ self.pylib_paths.add(canonical_path(m, directory=True))
+
+ if _structseq and not hasattr(_structseq, '__file__'):
+ # PyPy 2.4 has no __file__ in the builtin modules, but the code
+ # objects still have the file names. So dig into one to find
+ # the path to exclude. The "filename" might be synthetic,
+ # don't be fooled by those.
+ structseq_file = code_object(_structseq.structseq_new).co_filename
+ if not structseq_file.startswith("<"):
+ self.pylib_paths.add(canonical_path(structseq_file))
+
+ # To avoid tracing the coverage.py code itself, we skip anything
+ # located where we are.
+ self.cover_paths = [canonical_path(__file__, directory=True)]
+ if env.TESTING:
+ # Don't include our own test code.
+ self.cover_paths.append(os.path.join(self.cover_paths[0], "tests"))
+
+ # When testing, we use PyContracts, which should be considered
+ # part of coverage.py, and it uses six. Exclude those directories
+ # just as we exclude ourselves.
+ import contracts
+ import six
+ for mod in [contracts, six]:
+ self.cover_paths.append(canonical_path(mod))
+
+ # 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)
+ else:
+ if self.cover_paths:
+ self.cover_match = TreeMatcher(self.cover_paths)
+ if self.pylib_paths:
+ self.pylib_match = TreeMatcher(self.pylib_paths)
+ if self.include:
+ self.include_match = FnmatchMatcher(self.include)
+ if self.omit:
+ self.omit_match = FnmatchMatcher(self.omit)
+
+ def should_trace(self, filename, frame=None):
+ """Decide whether to trace execution in `filename`, with a reason.
+
+ This function is called from the trace function. As each new file name
+ is encountered, this function determines whether it is traced or not.
+
+ Returns a FileDisposition object.
+
+ """
+ original_filename = filename
+ disp = disposition_init(self.disp_class, filename)
+
+ def nope(disp, reason):
+ """Simple helper to make it easy to return NO."""
+ disp.trace = False
+ disp.reason = reason
+ return disp
+
+ if frame is not None:
+ # Compiled Python files have two file names: frame.f_code.co_filename is
+ # the file name at the time the .pyc was compiled. The second name is
+ # __file__, which is where the .pyc was actually loaded from. Since
+ # .pyc files can be moved after compilation (for example, by being
+ # installed), we look for __file__ in the frame and prefer it to the
+ # co_filename value.
+ dunder_file = frame.f_globals and frame.f_globals.get('__file__')
+ if dunder_file:
+ filename = source_for_file(dunder_file)
+ if original_filename and not original_filename.startswith('<'):
+ orig = os.path.basename(original_filename)
+ if orig != os.path.basename(filename):
+ # Files shouldn't be renamed when moved. This happens when
+ # exec'ing code. If it seems like something is wrong with
+ # the frame's file name, then just use the original.
+ filename = original_filename
+
+ if not filename:
+ # Empty string is pretty useless.
+ return nope(disp, "empty string isn't a file name")
+
+ if filename.startswith('memory:'):
+ return nope(disp, "memory isn't traceable")
+
+ if filename.startswith('<'):
+ # Lots of non-file execution is represented with artificial
+ # file names like "<string>", "<doctest readme.txt[0]>", or
+ # "<exec_function>". Don't ever trace these executions, since we
+ # can't do anything with the data later anyway.
+ return nope(disp, "not a real file name")
+
+ # pyexpat does a dumb thing, calling the trace function explicitly from
+ # C code with a C file name.
+ if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename):
+ return nope(disp, "pyexpat lies about itself")
+
+ # Jython reports the .class file to the tracer, use the source file.
+ if filename.endswith("$py.class"):
+ filename = filename[:-9] + ".py"
+
+ canonical = canonical_filename(filename)
+ disp.canonical_filename = canonical
+
+ # Try the plugins, see if they have an opinion about the file.
+ plugin = None
+ for plugin in self.plugins.file_tracers:
+ if not plugin._coverage_enabled:
+ continue
+
+ try:
+ file_tracer = plugin.file_tracer(canonical)
+ if file_tracer is not None:
+ file_tracer._coverage_plugin = plugin
+ disp.trace = True
+ disp.file_tracer = file_tracer
+ if file_tracer.has_dynamic_source_filename():
+ disp.has_dynamic_filename = True
+ else:
+ disp.source_filename = canonical_filename(
+ file_tracer.source_filename()
+ )
+ break
+ except Exception:
+ self.warn(
+ "Disabling plug-in %r due to an exception:" % (plugin._coverage_plugin_name)
+ )
+ traceback.print_exc()
+ plugin._coverage_enabled = False
+ continue
+ else:
+ # No plugin wanted it: it's Python.
+ disp.trace = True
+ disp.source_filename = canonical
+
+ if not disp.has_dynamic_filename:
+ if not disp.source_filename:
+ raise CoverageException(
+ "Plugin %r didn't set source_filename for %r" %
+ (plugin, disp.original_filename)
+ )
+ reason = self.check_include_omit_etc(disp.source_filename, frame)
+ if reason:
+ nope(disp, reason)
+
+ return disp
+
+ def check_include_omit_etc(self, filename, frame):
+ """Check a file name against the include, omit, etc, rules.
+
+ Returns a string or None. String means, don't trace, and is the reason
+ why. None means no reason found to not trace.
+
+ """
+ modulename = name_for_module(filename, frame)
+
+ # If the user specified source or include, then that's authoritative
+ # 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"
+ elif self.include_match:
+ if not self.include_match.match(filename):
+ return "falls outside the --include trees"
+ else:
+ # If we aren't supposed to trace installed code, then check if this
+ # is near the Python standard library and skip it if so.
+ if self.pylib_match and self.pylib_match.match(filename):
+ return "is in the stdlib"
+
+ # We exclude the coverage.py code itself, since a little of it
+ # will be measured otherwise.
+ if self.cover_match and self.cover_match.match(filename):
+ return "is part of coverage.py"
+
+ # Check the file against the omit pattern.
+ if self.omit_match and self.omit_match.match(filename):
+ return "is inside an --omit pattern"
+
+ # No point tracing a file we can't later write to SQLite.
+ try:
+ filename.encode("utf8")
+ except UnicodeEncodeError:
+ return "non-encodable filename"
+
+ # No reason found to skip this file.
+ return None
+
+ def warn_conflicting_settings(self):
+ """Warn if there are settings that conflict."""
+ if self.include:
+ if self.source or self.source_pkgs:
+ self.warn("--include is ignored because --source is set", slug="include-ignored")
+
+ def warn_already_imported_files(self):
+ """Warn if files have already been imported that we will be measuring."""
+ if self.include or self.source or self.source_pkgs:
+ warned = set()
+ for mod in list(sys.modules.values()):
+ filename = getattr(mod, "__file__", None)
+ if filename is None:
+ continue
+ if filename in warned:
+ continue
+
+ disp = self.should_trace(filename)
+ if disp.trace:
+ msg = "Already imported a file that will be measured: {}".format(filename)
+ self.warn(msg, slug="already-imported")
+ warned.add(filename)
+
+ def warn_unimported_source(self):
+ """Warn about source packages that were of interest, but never traced."""
+ for pkg in self.source_pkgs_unmatched:
+ self._warn_about_unmeasured_code(pkg)
+
+ def _warn_about_unmeasured_code(self, pkg):
+ """Warn about a package or module that we never traced.
+
+ `pkg` is a string, the name of the package or module.
+
+ """
+ mod = sys.modules.get(pkg)
+ if mod is None:
+ self.warn("Module %s was never imported." % pkg, slug="module-not-imported")
+ return
+
+ if module_is_namespace(mod):
+ # A namespace package. It's OK for this not to have been traced,
+ # since there is no code directly in it.
+ return
+
+ if not module_has_file(mod):
+ self.warn("Module %s has no Python source." % pkg, slug="module-not-python")
+ return
+
+ # The module was in sys.modules, and seems like a module with code, but
+ # we never measured it. I guess that means it was imported before
+ # coverage even started.
+ self.warn(
+ "Module %s was previously imported, but not measured" % pkg,
+ slug="module-not-measured",
+ )
+
+ def find_possibly_unexecuted_files(self):
+ """Find files in the areas of interest that might be untraced.
+
+ Yields pairs: file path, and responsible plug-in name.
+ """
+ for pkg in self.source_pkgs:
+ if (not pkg in sys.modules or
+ not module_has_file(sys.modules[pkg])):
+ continue
+ pkg_file = source_for_file(sys.modules[pkg].__file__)
+ for ret in self._find_executable_files(canonical_path(pkg_file)):
+ yield ret
+
+ for src in self.source:
+ for ret in self._find_executable_files(src):
+ yield ret
+
+ def _find_plugin_files(self, src_dir):
+ """Get executable files from the plugins."""
+ for plugin in self.plugins.file_tracers:
+ for x_file in plugin.find_executable_files(src_dir):
+ yield x_file, plugin._coverage_plugin_name
+
+ def _find_executable_files(self, src_dir):
+ """Find executable files in `src_dir`.
+
+ Search for files in `src_dir` that can be executed because they
+ are probably importable. Don't include ones that have been omitted
+ by the configuration.
+
+ Yield the file path, and the plugin name that handles the file.
+
+ """
+ py_files = ((py_file, None) for py_file in find_python_files(src_dir))
+ plugin_files = self._find_plugin_files(src_dir)
+
+ for file_path, plugin_name in itertools.chain(py_files, plugin_files):
+ file_path = canonical_filename(file_path)
+ if self.omit_match and self.omit_match.match(file_path):
+ # Turns out this file was omitted, so don't pull it back
+ # in as unexecuted.
+ continue
+ yield file_path, plugin_name
+
+ def sys_info(self):
+ """Our information for Coverage.sys_info.
+
+ Returns a list of (key, value) pairs.
+ """
+ info = [
+ ('cover_paths', self.cover_paths),
+ ('pylib_paths', self.pylib_paths),
+ ]
+
+ matcher_names = [
+ 'source_match', 'source_pkgs_match',
+ 'include_match', 'omit_match',
+ 'cover_match', 'pylib_match',
+ ]
+
+ for matcher_name in matcher_names:
+ matcher = getattr(self, matcher_name)
+ if matcher:
+ matcher_info = matcher.info()
+ else:
+ matcher_info = '-none-'
+ info.append((matcher_name, matcher_info))
+
+ return info
diff --git a/third_party/python/coverage/coverage/jsonreport.py b/third_party/python/coverage/coverage/jsonreport.py
new file mode 100644
index 0000000000..4287bc79a3
--- /dev/null
+++ b/third_party/python/coverage/coverage/jsonreport.py
@@ -0,0 +1,103 @@
+# coding: utf-8
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Json reporting for coverage.py"""
+import datetime
+import json
+import sys
+
+from coverage import __version__
+from coverage.report import get_analysis_to_report
+from coverage.results import Numbers
+
+
+class JsonReporter(object):
+ """A reporter for writing JSON coverage results."""
+
+ def __init__(self, coverage):
+ self.coverage = coverage
+ self.config = self.coverage.config
+ self.total = Numbers()
+ self.report_data = {}
+
+ def report(self, morfs, outfile=None):
+ """Generate a json report for `morfs`.
+
+ `morfs` is a list of modules or file names.
+
+ `outfile` is a file object to write the json to
+
+ """
+ outfile = outfile or sys.stdout
+ coverage_data = self.coverage.get_data()
+ coverage_data.set_query_contexts(self.config.report_contexts)
+ self.report_data["meta"] = {
+ "version": __version__,
+ "timestamp": datetime.datetime.now().isoformat(),
+ "branch_coverage": coverage_data.has_arcs(),
+ "show_contexts": self.config.json_show_contexts,
+ }
+
+ measured_files = {}
+ for file_reporter, analysis in get_analysis_to_report(self.coverage, morfs):
+ measured_files[file_reporter.relative_filename()] = self.report_one_file(
+ coverage_data,
+ analysis
+ )
+
+ self.report_data["files"] = measured_files
+
+ self.report_data["totals"] = {
+ 'covered_lines': self.total.n_executed,
+ 'num_statements': self.total.n_statements,
+ 'percent_covered': self.total.pc_covered,
+ 'missing_lines': self.total.n_missing,
+ 'excluded_lines': self.total.n_excluded,
+ }
+
+ if coverage_data.has_arcs():
+ 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(
+ self.report_data,
+ outfile,
+ indent=4 if self.config.json_pretty_print else None
+ )
+
+ return self.total.n_statements and self.total.pc_covered
+
+ def report_one_file(self, coverage_data, analysis):
+ """Extract the relevant report data for a single file"""
+ nums = analysis.numbers
+ self.total += nums
+ summary = {
+ 'covered_lines': nums.n_executed,
+ 'num_statements': nums.n_statements,
+ 'percent_covered': nums.pc_covered,
+ 'missing_lines': nums.n_missing,
+ 'excluded_lines': nums.n_excluded,
+ }
+ reported_file = {
+ 'executed_lines': sorted(analysis.executed),
+ 'summary': summary,
+ 'missing_lines': sorted(analysis.missing),
+ 'excluded_lines': sorted(analysis.excluded)
+ }
+ if self.config.json_show_contexts:
+ reported_file['contexts'] = analysis.data.contexts_by_lineno(
+ analysis.filename,
+ )
+ if coverage_data.has_arcs():
+ 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
diff --git a/third_party/python/coverage/coverage/misc.py b/third_party/python/coverage/coverage/misc.py
new file mode 100644
index 0000000000..5c4381ab65
--- /dev/null
+++ b/third_party/python/coverage/coverage/misc.py
@@ -0,0 +1,361 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Miscellaneous stuff for coverage.py."""
+
+import errno
+import hashlib
+import inspect
+import locale
+import os
+import os.path
+import random
+import re
+import socket
+import sys
+import types
+
+from coverage import env
+from coverage.backward import to_bytes, unicode_class
+
+ISOLATED_MODULES = {}
+
+
+def isolate_module(mod):
+ """Copy a module so that we are isolated from aggressive mocking.
+
+ If a test suite mocks os.path.exists (for example), and then we need to use
+ it during the test, everything will get tangled up if we use their mock.
+ Making a copy of the module when we import it will isolate coverage.py from
+ those complications.
+ """
+ if mod not in ISOLATED_MODULES:
+ new_mod = types.ModuleType(mod.__name__)
+ ISOLATED_MODULES[mod] = new_mod
+ for name in dir(mod):
+ value = getattr(mod, name)
+ if isinstance(value, types.ModuleType):
+ value = isolate_module(value)
+ setattr(new_mod, name, value)
+ return ISOLATED_MODULES[mod]
+
+os = isolate_module(os)
+
+
+def dummy_decorator_with_args(*args_unused, **kwargs_unused):
+ """Dummy no-op implementation of a decorator with arguments."""
+ def _decorator(func):
+ return func
+ return _decorator
+
+
+# Environment COVERAGE_NO_CONTRACTS=1 can turn off contracts while debugging
+# tests to remove noise from stack traces.
+# $set_env.py: COVERAGE_NO_CONTRACTS - Disable PyContracts to simplify stack traces.
+USE_CONTRACTS = env.TESTING and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
+
+# Use PyContracts for assertion testing on parameters and returns, but only if
+# we are running our own test suite.
+if USE_CONTRACTS:
+ from contracts import contract # pylint: disable=unused-import
+ from contracts import new_contract as raw_new_contract
+
+ def new_contract(*args, **kwargs):
+ """A proxy for contracts.new_contract that doesn't mind happening twice."""
+ try:
+ return 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.
+ pass
+
+ # Define contract words that PyContract doesn't have.
+ new_contract('bytes', lambda v: isinstance(v, bytes))
+ if env.PY3:
+ new_contract('unicode', lambda v: isinstance(v, unicode_class))
+
+ 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(","))
+ def _wrapper(*args, **kwargs):
+ vals = [kwargs.get(name) for name in argnameset]
+ assert sum(val is not None for val in vals) == 1
+ return func(*args, **kwargs)
+ return _wrapper
+ return _decorator
+else: # pragma: not testing
+ # We aren't using real PyContracts, so just define our decorators as
+ # stunt-double no-ops.
+ contract = dummy_decorator_with_args
+ one_of = dummy_decorator_with_args
+
+ def new_contract(*args_unused, **kwargs_unused):
+ """Dummy no-op implementation of `new_contract`."""
+ pass
+
+
+def nice_pair(pair):
+ """Make a nice string representation of a pair of numbers.
+
+ If the numbers are equal, just return the number, otherwise return the pair
+ with a dash between them, indicating the range.
+
+ """
+ start, end = pair
+ if start == end:
+ return "%d" % start
+ else:
+ return "%d-%d" % (start, end)
+
+
+def expensive(fn):
+ """A decorator to indicate that a method shouldn't be called more than once.
+
+ Normally, this does nothing. During testing, this raises an exception if
+ called more than once.
+
+ """
+ if env.TESTING:
+ attr = "_once_" + fn.__name__
+
+ def _wrapper(self):
+ if hasattr(self, attr):
+ raise AssertionError("Shouldn't have called %s more than once" % fn.__name__)
+ setattr(self, attr, True)
+ return fn(self)
+ return _wrapper
+ else:
+ return fn # pragma: not testing
+
+
+def bool_or_none(b):
+ """Return bool(b), but preserve None."""
+ if b is None:
+ return None
+ else:
+ return bool(b)
+
+
+def join_regex(regexes):
+ """Combine a list of regexes into one that matches any of them."""
+ return "|".join("(?:%s)" % r for r in regexes)
+
+
+def file_be_gone(path):
+ """Remove a file, and don't get annoyed if it doesn't exist."""
+ try:
+ os.remove(path)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
+def ensure_dir(directory):
+ """Make sure the directory exists.
+
+ If `directory` is None or empty, do nothing.
+ """
+ if directory and not os.path.isdir(directory):
+ os.makedirs(directory)
+
+
+def ensure_dir_for_file(path):
+ """Make sure the directory for the path exists."""
+ ensure_dir(os.path.dirname(path))
+
+
+def output_encoding(outfile=None):
+ """Determine the encoding to use for output written to `outfile` or stdout."""
+ if outfile is None:
+ outfile = sys.stdout
+ encoding = (
+ getattr(outfile, "encoding", None) or
+ getattr(sys.__stdout__, "encoding", None) or
+ locale.getpreferredencoding()
+ )
+ return encoding
+
+
+def filename_suffix(suffix):
+ """Compute a filename suffix for a data file.
+
+ If `suffix` is a string or None, simply return it. If `suffix` is True,
+ then build a suffix incorporating the hostname, process id, and a random
+ number.
+
+ Returns a string or None.
+
+ """
+ if suffix is True:
+ # If data_suffix was a simple true value, then make a suffix with
+ # plenty of distinguishing information. We do this here in
+ # `save()` at the last minute so that the pid will be correct even
+ # if the process forks.
+ dice = random.Random(os.urandom(8)).randint(0, 999999)
+ suffix = "%s.%s.%06d" % (socket.gethostname(), os.getpid(), dice)
+ return suffix
+
+
+class Hasher(object):
+ """Hashes Python data into md5."""
+ def __init__(self):
+ self.md5 = hashlib.md5()
+
+ def update(self, v):
+ """Add `v` to the hash, recursively if needed."""
+ self.md5.update(to_bytes(str(type(v))))
+ if isinstance(v, unicode_class):
+ self.md5.update(v.encode('utf8'))
+ elif isinstance(v, bytes):
+ self.md5.update(v)
+ elif v is None:
+ pass
+ elif isinstance(v, (int, float)):
+ self.md5.update(to_bytes(str(v)))
+ elif isinstance(v, (tuple, list)):
+ for e in v:
+ self.update(e)
+ elif isinstance(v, dict):
+ keys = v.keys()
+ for k in sorted(keys):
+ self.update(k)
+ self.update(v[k])
+ else:
+ for k in dir(v):
+ if k.startswith('__'):
+ continue
+ a = getattr(v, k)
+ if inspect.isroutine(a):
+ continue
+ self.update(k)
+ self.update(a)
+ self.md5.update(b'.')
+
+ def hexdigest(self):
+ """Retrieve the hex digest of the hash."""
+ return self.md5.hexdigest()
+
+
+def _needs_to_implement(that, func_name):
+ """Helper to raise NotImplementedError in interface stubs."""
+ if hasattr(that, "_coverage_plugin_name"):
+ thing = "Plugin"
+ name = that._coverage_plugin_name
+ else:
+ thing = "Class"
+ klass = that.__class__
+ name = "{klass.__module__}.{klass.__name__}".format(klass=klass)
+
+ raise NotImplementedError(
+ "{thing} {name!r} needs to implement {func_name}()".format(
+ thing=thing, name=name, func_name=func_name
+ )
+ )
+
+
+class DefaultValue(object):
+ """A sentinel object to use for unusual default-value needs.
+
+ Construct with a string that will be used as the repr, for display in help
+ and Sphinx output.
+
+ """
+ def __init__(self, display_as):
+ self.display_as = display_as
+
+ def __repr__(self):
+ return self.display_as
+
+
+def substitute_variables(text, variables):
+ """Substitute ``${VAR}`` variables in `text` with their values.
+
+ Variables in the text can take a number of shell-inspired forms::
+
+ $VAR
+ ${VAR}
+ ${VAR?} strict: an error if VAR isn't defined.
+ ${VAR-missing} defaulted: "missing" if VAR isn't defined.
+ $$ just a dollar sign.
+
+ `variables` is a dictionary of variable values.
+
+ Returns the resulting text with values substituted.
+
+ """
+ dollar_pattern = r"""(?x) # Use extended regex syntax
+ \$ # A dollar sign,
+ (?: # then
+ (?P<dollar>\$) | # a dollar sign, or
+ (?P<word1>\w+) | # a plain word, or
+ { # a {-wrapped
+ (?P<word2>\w+) # word,
+ (?:
+ (?P<strict>\?) | # with a strict marker
+ -(?P<defval>[^}]*) # or a default value
+ )? # maybe.
+ }
+ )
+ """
+
+ def dollar_replace(match):
+ """Called for each $replacement."""
+ # Only one of the groups will have matched, just get its text.
+ word = next(g for g in match.group('dollar', 'word1', 'word2') if g)
+ if word == "$":
+ return "$"
+ elif word in variables:
+ return variables[word]
+ elif match.group('strict'):
+ msg = "Variable {} is undefined: {!r}".format(word, text)
+ raise CoverageException(msg)
+ else:
+ return match.group('defval')
+
+ text = re.sub(dollar_pattern, dollar_replace, text)
+ return text
+
+
+class BaseCoverageException(Exception):
+ """The base of all Coverage exceptions."""
+ pass
+
+
+class CoverageException(BaseCoverageException):
+ """An exception raised by a coverage.py function."""
+ pass
+
+
+class NoSource(CoverageException):
+ """We couldn't find the source for a module."""
+ pass
+
+
+class NoCode(NoSource):
+ """We couldn't find any code at all."""
+ pass
+
+
+class NotPython(CoverageException):
+ """A source file turned out not to be parsable Python."""
+ pass
+
+
+class ExceptionDuringRun(CoverageException):
+ """An exception happened while running customer code.
+
+ Construct it with three arguments, the values from `sys.exc_info`.
+
+ """
+ pass
+
+
+class StopEverything(BaseCoverageException):
+ """An exception that means everything should stop.
+
+ The CoverageTest class converts these to SkipTest, so that when running
+ tests, raising this exception will automatically skip the test.
+
+ """
+ pass
diff --git a/third_party/python/coverage/coverage/multiproc.py b/third_party/python/coverage/coverage/multiproc.py
new file mode 100644
index 0000000000..2931b3be0e
--- /dev/null
+++ b/third_party/python/coverage/coverage/multiproc.py
@@ -0,0 +1,111 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Monkey-patching to add multiprocessing support for coverage.py"""
+
+import multiprocessing
+import multiprocessing.process
+import os
+import os.path
+import sys
+import traceback
+
+from coverage import env
+from coverage.misc import contract
+
+# An attribute that will be set on the module to indicate that it has been
+# monkey-patched.
+PATCHED_MARKER = "_coverage$patched"
+
+
+if env.PYVERSION >= (3, 4):
+ OriginalProcess = multiprocessing.process.BaseProcess
+else:
+ OriginalProcess = multiprocessing.Process
+
+original_bootstrap = OriginalProcess._bootstrap
+
+class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method
+ """A replacement for multiprocess.Process that starts coverage."""
+
+ def _bootstrap(self, *args, **kwargs): # pylint: disable=arguments-differ
+ """Wrapper around _bootstrap to start coverage."""
+ try:
+ from coverage import Coverage # avoid circular import
+ cov = Coverage(data_suffix=True)
+ cov._warn_preimported_source = False
+ cov.start()
+ debug = cov._debug
+ if debug.should("multiproc"):
+ debug.write("Calling multiprocessing bootstrap")
+ except Exception:
+ print("Exception during multiprocessing bootstrap init:")
+ traceback.print_exc(file=sys.stdout)
+ sys.stdout.flush()
+ raise
+ try:
+ return original_bootstrap(self, *args, **kwargs)
+ finally:
+ if debug.should("multiproc"):
+ debug.write("Finished multiprocessing bootstrap")
+ cov.stop()
+ cov.save()
+ if debug.should("multiproc"):
+ debug.write("Saved multiprocessing data")
+
+class Stowaway(object):
+ """An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
+ def __init__(self, rcfile):
+ self.rcfile = rcfile
+
+ def __getstate__(self):
+ return {'rcfile': self.rcfile}
+
+ def __setstate__(self, state):
+ patch_multiprocessing(state['rcfile'])
+
+
+@contract(rcfile=str)
+def patch_multiprocessing(rcfile):
+ """Monkey-patch the multiprocessing module.
+
+ This enables coverage measurement of processes started by multiprocessing.
+ This involves aggressive monkey-patching.
+
+ `rcfile` is the path to the rcfile being used.
+
+ """
+
+ if hasattr(multiprocessing, PATCHED_MARKER):
+ return
+
+ if env.PYVERSION >= (3, 4):
+ OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap
+ else:
+ multiprocessing.Process = ProcessWithCoverage
+
+ # Set the value in ProcessWithCoverage that will be pickled into the child
+ # process.
+ os.environ["COVERAGE_RCFILE"] = os.path.abspath(rcfile)
+
+ # When spawning processes rather than forking them, we have no state in the
+ # new process. We sneak in there with a Stowaway: we stuff one of our own
+ # objects into the data that gets pickled and sent to the sub-process. When
+ # the Stowaway is unpickled, it's __setstate__ method is called, which
+ # re-applies the monkey-patch.
+ # Windows only spawns, so this is needed to keep Windows working.
+ try:
+ from multiprocessing import spawn
+ original_get_preparation_data = spawn.get_preparation_data
+ except (ImportError, AttributeError):
+ pass
+ else:
+ def get_preparation_data_with_stowaway(name):
+ """Get the original preparation data, and also insert our stowaway."""
+ d = original_get_preparation_data(name)
+ d['stowaway'] = Stowaway(rcfile)
+ return d
+
+ spawn.get_preparation_data = get_preparation_data_with_stowaway
+
+ setattr(multiprocessing, PATCHED_MARKER, True)
diff --git a/third_party/python/coverage/coverage/numbits.py b/third_party/python/coverage/coverage/numbits.py
new file mode 100644
index 0000000000..6ca96fbcf7
--- /dev/null
+++ b/third_party/python/coverage/coverage/numbits.py
@@ -0,0 +1,163 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""
+Functions to manipulate packed binary representations of number sets.
+
+To save space, coverage stores sets of line numbers in SQLite using a packed
+binary representation called a numbits. A numbits is a set of positive
+integers.
+
+A numbits is stored as a blob in the database. The exact meaning of the bytes
+in the blobs should be considered an implementation detail that might change in
+the future. Use these functions to work with those binary blobs of data.
+
+"""
+import json
+
+from coverage import env
+from coverage.backward import byte_to_int, bytes_to_ints, binary_bytes, zip_longest
+from coverage.misc import contract, new_contract
+
+if env.PY3:
+ def _to_blob(b):
+ """Convert a bytestring into a type SQLite will accept for a blob."""
+ return b
+
+ new_contract('blob', lambda v: isinstance(v, bytes))
+else:
+ def _to_blob(b):
+ """Convert a bytestring into a type SQLite will accept for a blob."""
+ return buffer(b) # pylint: disable=undefined-variable
+
+ new_contract('blob', lambda v: isinstance(v, buffer)) # pylint: disable=undefined-variable
+
+
+@contract(nums='Iterable', returns='blob')
+def nums_to_numbits(nums):
+ """Convert `nums` into a numbits.
+
+ Arguments:
+ nums: a reusable iterable of integers, the line numbers to store.
+
+ Returns:
+ A binary blob.
+ """
+ try:
+ nbytes = max(nums) // 8 + 1
+ except ValueError:
+ # nums was empty.
+ return _to_blob(b'')
+ b = bytearray(nbytes)
+ for num in nums:
+ b[num//8] |= 1 << num % 8
+ return _to_blob(bytes(b))
+
+
+@contract(numbits='blob', returns='list[int]')
+def numbits_to_nums(numbits):
+ """Convert a numbits into a list of numbers.
+
+ Arguments:
+ numbits: a binary blob, the packed number set.
+
+ Returns:
+ A list of ints.
+
+ When registered as a SQLite function by :func:`register_sqlite_functions`,
+ this returns a string, a JSON-encoded list of ints.
+
+ """
+ nums = []
+ for byte_i, byte in enumerate(bytes_to_ints(numbits)):
+ for bit_i in range(8):
+ if (byte & (1 << bit_i)):
+ nums.append(byte_i * 8 + bit_i)
+ return nums
+
+
+@contract(numbits1='blob', numbits2='blob', returns='blob')
+def numbits_union(numbits1, numbits2):
+ """Compute the union of two numbits.
+
+ Returns:
+ A new numbits, the union of `numbits1` and `numbits2`.
+ """
+ byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0)
+ return _to_blob(binary_bytes(b1 | b2 for b1, b2 in byte_pairs))
+
+
+@contract(numbits1='blob', numbits2='blob', returns='blob')
+def numbits_intersection(numbits1, numbits2):
+ """Compute the intersection of two numbits.
+
+ Returns:
+ A new numbits, the intersection `numbits1` and `numbits2`.
+ """
+ byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0)
+ intersection_bytes = binary_bytes(b1 & b2 for b1, b2 in byte_pairs)
+ return _to_blob(intersection_bytes.rstrip(b'\0'))
+
+
+@contract(numbits1='blob', numbits2='blob', returns='bool')
+def numbits_any_intersection(numbits1, numbits2):
+ """Is there any number that appears in both numbits?
+
+ Determine whether two number sets have a non-empty intersection. This is
+ faster than computing the intersection.
+
+ Returns:
+ A bool, True if there is any number in both `numbits1` and `numbits2`.
+ """
+ byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0)
+ return any(b1 & b2 for b1, b2 in byte_pairs)
+
+
+@contract(num='int', numbits='blob', returns='bool')
+def num_in_numbits(num, numbits):
+ """Does the integer `num` appear in `numbits`?
+
+ Returns:
+ A bool, True if `num` is a member of `numbits`.
+ """
+ nbyte, nbit = divmod(num, 8)
+ if nbyte >= len(numbits):
+ return False
+ return bool(byte_to_int(numbits[nbyte]) & (1 << nbit))
+
+
+def register_sqlite_functions(connection):
+ """
+ Define numbits functions in a SQLite connection.
+
+ This defines these functions for use in SQLite statements:
+
+ * :func:`numbits_union`
+ * :func:`numbits_intersection`
+ * :func:`numbits_any_intersection`
+ * :func:`num_in_numbits`
+ * :func:`numbits_to_nums`
+
+ `connection` is a :class:`sqlite3.Connection <python:sqlite3.Connection>`
+ object. After creating the connection, pass it to this function to
+ register the numbits functions. Then you can use numbits functions in your
+ queries::
+
+ import sqlite3
+ from coverage.numbits import register_sqlite_functions
+
+ conn = sqlite3.connect('example.db')
+ register_sqlite_functions(conn)
+ c = conn.cursor()
+ # Kind of a nonsense query: find all the files and contexts that
+ # executed line 47 in any file:
+ c.execute(
+ "select file_id, context_id from line_bits where num_in_numbits(?, numbits)",
+ (47,)
+ )
+ """
+ connection.create_function("numbits_union", 2, numbits_union)
+ connection.create_function("numbits_intersection", 2, numbits_intersection)
+ connection.create_function("numbits_any_intersection", 2, numbits_any_intersection)
+ connection.create_function("num_in_numbits", 2, num_in_numbits)
+ connection.create_function("numbits_to_nums", 1, lambda b: json.dumps(numbits_to_nums(b)))
diff --git a/third_party/python/coverage/coverage/optional.py b/third_party/python/coverage/coverage/optional.py
new file mode 100644
index 0000000000..ee617b625b
--- /dev/null
+++ b/third_party/python/coverage/coverage/optional.py
@@ -0,0 +1,68 @@
+# 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
+ 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
diff --git a/third_party/python/coverage/coverage/parser.py b/third_party/python/coverage/coverage/parser.py
new file mode 100644
index 0000000000..e3e4314902
--- /dev/null
+++ b/third_party/python/coverage/coverage/parser.py
@@ -0,0 +1,1251 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Code parsing for coverage.py."""
+
+import ast
+import collections
+import os
+import re
+import token
+import tokenize
+
+from coverage import env
+from coverage.backward import range # pylint: disable=redefined-builtin
+from coverage.backward import bytes_to_ints, string_class
+from coverage.bytecode import code_objects
+from coverage.debug import short_stack
+from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of
+from coverage.misc import NoSource, NotPython, StopEverything
+from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration
+
+
+class PythonParser(object):
+ """Parse code to find executable lines, excluded lines, etc.
+
+ This information is all based on static analysis: no code execution is
+ involved.
+
+ """
+ @contract(text='unicode|None')
+ def __init__(self, text=None, filename=None, exclude=None):
+ """
+ Source can be provided as `text`, the text itself, or `filename`, from
+ which the text will be read. Excluded lines are those that match
+ `exclude`, a regex.
+
+ """
+ assert text or filename, "PythonParser needs either text or filename"
+ self.filename = filename or "<code>"
+ self.text = text
+ if not self.text:
+ from coverage.python import get_python_source
+ try:
+ self.text = get_python_source(self.filename)
+ except IOError as err:
+ raise NoSource(
+ "No source for code: '%s': %s" % (self.filename, err)
+ )
+
+ self.exclude = exclude
+
+ # The text lines of the parsed code.
+ self.lines = self.text.split('\n')
+
+ # The normalized line numbers of the statements in the code. Exclusions
+ # are taken into account, and statements are adjusted to their first
+ # lines.
+ self.statements = set()
+
+ # The normalized line numbers of the excluded lines in the code,
+ # adjusted to their first lines.
+ self.excluded = set()
+
+ # The raw_* attributes are only used in this class, and in
+ # lab/parser.py to show how this class is working.
+
+ # The line numbers that start statements, as reported by the line
+ # number table in the bytecode.
+ self.raw_statements = set()
+
+ # The raw line numbers of excluded lines of code, as marked by pragmas.
+ self.raw_excluded = set()
+
+ # The line numbers of class and function definitions.
+ self.raw_classdefs = set()
+
+ # The line numbers of docstring lines.
+ self.raw_docstrings = set()
+
+ # Internal detail, used by lab/parser.py.
+ self.show_tokens = False
+
+ # A dict mapping line numbers to lexical statement starts for
+ # multi-line statements.
+ self._multiline = {}
+
+ # Lazily-created ByteParser, arc data, and missing arc descriptions.
+ self._byte_parser = None
+ self._all_arcs = None
+ self._missing_arc_fragments = None
+
+ @property
+ def byte_parser(self):
+ """Create a ByteParser on demand."""
+ if not self._byte_parser:
+ self._byte_parser = ByteParser(self.text, filename=self.filename)
+ return self._byte_parser
+
+ def lines_matching(self, *regexes):
+ """Find the lines matching one of a list of regexes.
+
+ Returns a set of line numbers, the lines that contain a match for one
+ of the regexes in `regexes`. The entire line needn't match, just a
+ part of it.
+
+ """
+ combined = join_regex(regexes)
+ if env.PY2:
+ combined = combined.decode("utf8")
+ regex_c = re.compile(combined)
+ matches = set()
+ for i, ltext in enumerate(self.lines, start=1):
+ if regex_c.search(ltext):
+ matches.add(i)
+ return matches
+
+ def _raw_parse(self):
+ """Parse the source to find the interesting facts about its lines.
+
+ A handful of attributes are updated.
+
+ """
+ # Find lines which match an exclusion pattern.
+ if self.exclude:
+ self.raw_excluded = self.lines_matching(self.exclude)
+
+ # Tokenize, to find excluded suites, to find docstrings, and to find
+ # multi-line statements.
+ indent = 0
+ exclude_indent = 0
+ excluding = False
+ excluding_decorators = False
+ prev_toktype = token.INDENT
+ first_line = None
+ empty = True
+ first_on_line = True
+
+ tokgen = generate_tokens(self.text)
+ for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
+ if self.show_tokens: # pragma: debugging
+ print("%10s %5s %-20r %r" % (
+ tokenize.tok_name.get(toktype, toktype),
+ nice_pair((slineno, elineno)), ttext, ltext
+ ))
+ if toktype == token.INDENT:
+ indent += 1
+ elif toktype == token.DEDENT:
+ indent -= 1
+ elif toktype == token.NAME:
+ if ttext == 'class':
+ # Class definitions look like branches in the bytecode, so
+ # we need to exclude them. The simplest way is to note the
+ # lines with the 'class' keyword.
+ self.raw_classdefs.add(slineno)
+ elif toktype == token.OP:
+ if ttext == ':':
+ should_exclude = (elineno in self.raw_excluded) or excluding_decorators
+ if not excluding and should_exclude:
+ # Start excluding a suite. We trigger off of the colon
+ # token so that the #pragma comment will be recognized on
+ # the same line as the colon.
+ self.raw_excluded.add(elineno)
+ exclude_indent = indent
+ excluding = True
+ excluding_decorators = False
+ elif ttext == '@' and first_on_line:
+ # A decorator.
+ if elineno in self.raw_excluded:
+ excluding_decorators = True
+ if excluding_decorators:
+ self.raw_excluded.add(elineno)
+ elif toktype == token.STRING and prev_toktype == token.INDENT:
+ # Strings that are first on an indented line are docstrings.
+ # (a trick from trace.py in the stdlib.) This works for
+ # 99.9999% of cases. For the rest (!) see:
+ # http://stackoverflow.com/questions/1769332/x/1769794#1769794
+ self.raw_docstrings.update(range(slineno, elineno+1))
+ elif toktype == token.NEWLINE:
+ if first_line is not None and elineno != first_line:
+ # We're at the end of a line, and we've ended on a
+ # different line than the first line of the statement,
+ # so record a multi-line range.
+ for l in range(first_line, elineno+1):
+ self._multiline[l] = first_line
+ first_line = None
+ first_on_line = True
+
+ if ttext.strip() and toktype != tokenize.COMMENT:
+ # A non-whitespace token.
+ empty = False
+ if first_line is None:
+ # The token is not whitespace, and is the first in a
+ # statement.
+ first_line = slineno
+ # Check whether to end an excluded suite.
+ if excluding and indent <= exclude_indent:
+ excluding = False
+ if excluding:
+ self.raw_excluded.add(elineno)
+ first_on_line = False
+
+ prev_toktype = toktype
+
+ # Find the starts of the executable statements.
+ if not empty:
+ self.raw_statements.update(self.byte_parser._find_statements())
+
+ def first_line(self, line):
+ """Return the first line number of the statement including `line`."""
+ if line < 0:
+ line = -self._multiline.get(-line, -line)
+ else:
+ line = self._multiline.get(line, line)
+ return line
+
+ def first_lines(self, lines):
+ """Map the line numbers in `lines` to the correct first line of the
+ statement.
+
+ Returns a set of the first lines.
+
+ """
+ return set(self.first_line(l) for l in lines)
+
+ def translate_lines(self, lines):
+ """Implement `FileReporter.translate_lines`."""
+ return self.first_lines(lines)
+
+ def translate_arcs(self, arcs):
+ """Implement `FileReporter.translate_arcs`."""
+ return [(self.first_line(a), self.first_line(b)) for (a, b) in arcs]
+
+ def parse_source(self):
+ """Parse source text to find executable lines, excluded lines, etc.
+
+ Sets the .excluded and .statements attributes, normalized to the first
+ line of multi-line statements.
+
+ """
+ try:
+ self._raw_parse()
+ except (tokenize.TokenError, IndentationError) as err:
+ if hasattr(err, "lineno"):
+ lineno = err.lineno # IndentationError
+ else:
+ lineno = err.args[1][0] # TokenError
+ raise NotPython(
+ u"Couldn't parse '%s' as Python source: '%s' at line %d" % (
+ self.filename, err.args[0], lineno
+ )
+ )
+
+ self.excluded = self.first_lines(self.raw_excluded)
+
+ ignore = self.excluded | self.raw_docstrings
+ starts = self.raw_statements - ignore
+ self.statements = self.first_lines(starts) - ignore
+
+ def arcs(self):
+ """Get information about the arcs available in the code.
+
+ Returns a set of line number pairs. Line numbers have been normalized
+ to the first line of multi-line statements.
+
+ """
+ if self._all_arcs is None:
+ self._analyze_ast()
+ return self._all_arcs
+
+ def _analyze_ast(self):
+ """Run the AstArcAnalyzer and save its results.
+
+ `_all_arcs` is the set of arcs in the code.
+
+ """
+ aaa = AstArcAnalyzer(self.text, self.raw_statements, self._multiline)
+ aaa.analyze()
+
+ self._all_arcs = set()
+ for l1, l2 in aaa.arcs:
+ fl1 = self.first_line(l1)
+ fl2 = self.first_line(l2)
+ if fl1 != fl2:
+ self._all_arcs.add((fl1, fl2))
+
+ self._missing_arc_fragments = aaa.missing_arc_fragments
+
+ def exit_counts(self):
+ """Get a count of exits from that each line.
+
+ Excluded lines are excluded.
+
+ """
+ exit_counts = collections.defaultdict(int)
+ for l1, l2 in self.arcs():
+ if l1 < 0:
+ # Don't ever report -1 as a line number
+ continue
+ if l1 in self.excluded:
+ # Don't report excluded lines as line numbers.
+ continue
+ if l2 in self.excluded:
+ # Arcs to excluded lines shouldn't count.
+ continue
+ exit_counts[l1] += 1
+
+ # Class definitions have one extra exit, so remove one for each:
+ for l in self.raw_classdefs:
+ # Ensure key is there: class definitions can include excluded lines.
+ if l in exit_counts:
+ exit_counts[l] -= 1
+
+ return exit_counts
+
+ def missing_arc_description(self, start, end, executed_arcs=None):
+ """Provide an English sentence describing a missing arc."""
+ if self._missing_arc_fragments is None:
+ self._analyze_ast()
+
+ actual_start = start
+
+ if (
+ executed_arcs and
+ end < 0 and end == -start and
+ (end, start) not in executed_arcs and
+ (end, start) in self._missing_arc_fragments
+ ):
+ # It's a one-line callable, and we never even started it,
+ # and we have a message about not starting it.
+ start, end = end, start
+
+ fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)])
+
+ msgs = []
+ for fragment_pair in fragment_pairs:
+ smsg, emsg = fragment_pair
+
+ if emsg is None:
+ if end < 0:
+ # Hmm, maybe we have a one-line callable, let's check.
+ if (-end, end) in self._missing_arc_fragments:
+ return self.missing_arc_description(-end, end)
+ emsg = "didn't jump to the function exit"
+ else:
+ emsg = "didn't jump to line {lineno}"
+ emsg = emsg.format(lineno=end)
+
+ msg = "line {start} {emsg}".format(start=actual_start, emsg=emsg)
+ if smsg is not None:
+ msg += ", because {smsg}".format(smsg=smsg.format(lineno=actual_start))
+
+ msgs.append(msg)
+
+ return " or ".join(msgs)
+
+
+class ByteParser(object):
+ """Parse bytecode to understand the structure of code."""
+
+ @contract(text='unicode')
+ def __init__(self, text, code=None, filename=None):
+ self.text = text
+ if code:
+ self.code = code
+ else:
+ try:
+ self.code = compile_unicode(text, filename, "exec")
+ except SyntaxError as synerr:
+ raise NotPython(
+ u"Couldn't parse '%s' as Python source: '%s' at line %d" % (
+ filename, synerr.msg, synerr.lineno
+ )
+ )
+
+ # Alternative Python implementations don't always provide all the
+ # attributes on code objects that we need to do the analysis.
+ for attr in ['co_lnotab', 'co_firstlineno']:
+ if not hasattr(self.code, attr):
+ raise StopEverything( # pragma: only jython
+ "This implementation of Python doesn't support code analysis.\n"
+ "Run coverage.py under another Python for this command."
+ )
+
+ def child_parsers(self):
+ """Iterate over all the code objects nested within this one.
+
+ The iteration includes `self` as its first value.
+
+ """
+ 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), ...
+
+ Only byte offsets that correspond to line numbers are included in the
+ results.
+
+ """
+ # 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)
+
+ def _find_statements(self):
+ """Find the statements in `self.code`.
+
+ Produce a sequence of line numbers that start statements. Recurses
+ into all code objects reachable from `self.code`.
+
+ """
+ for bp in self.child_parsers():
+ # Get all of the lineno information from this code.
+ for _, l in bp._bytes_lines():
+ yield l
+
+
+#
+# AST analysis
+#
+
+class LoopBlock(object):
+ """A block on the block stack representing a `for` or `while` loop."""
+ @contract(start=int)
+ def __init__(self, start):
+ # The line number where the loop starts.
+ self.start = start
+ # A set of ArcStarts, the arcs from break statements exiting this loop.
+ self.break_exits = set()
+
+
+class FunctionBlock(object):
+ """A block on the block stack representing a function definition."""
+ @contract(start=int, name=str)
+ def __init__(self, start, name):
+ # The line number where the function starts.
+ self.start = start
+ # The name of the function.
+ self.name = name
+
+
+class TryBlock(object):
+ """A block on the block stack representing a `try` block."""
+ @contract(handler_start='int|None', final_start='int|None')
+ def __init__(self, handler_start, final_start):
+ # The line number of the first "except" handler, if any.
+ self.handler_start = handler_start
+ # The line number of the "finally:" clause, if any.
+ self.final_start = final_start
+
+ # The ArcStarts for breaks/continues/returns/raises inside the "try:"
+ # that need to route through the "finally:" clause.
+ self.break_from = set()
+ self.continue_from = set()
+ self.return_from = set()
+ self.raise_from = set()
+
+
+class ArcStart(collections.namedtuple("Arc", "lineno, cause")):
+ """The information needed to start an arc.
+
+ `lineno` is the line number the arc starts from.
+
+ `cause` is an English text fragment used as the `startmsg` for
+ AstArcAnalyzer.missing_arc_fragments. It will be used to describe why an
+ arc wasn't executed, so should fit well into a sentence of the form,
+ "Line 17 didn't run because {cause}." The fragment can include "{lineno}"
+ to have `lineno` interpolated into it.
+
+ """
+ def __new__(cls, lineno, cause=None):
+ return super(ArcStart, cls).__new__(cls, lineno, cause)
+
+
+# Define contract words that PyContract doesn't have.
+# ArcStarts is for a list or set of ArcStart's.
+new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq))
+
+
+# Turn on AST dumps with an environment variable.
+# $set_env.py: COVERAGE_AST_DUMP - Dump the AST nodes when parsing code.
+AST_DUMP = bool(int(os.environ.get("COVERAGE_AST_DUMP", 0)))
+
+class NodeList(object):
+ """A synthetic fictitious node, containing a sequence of nodes.
+
+ This is used when collapsing optimized if-statements, to represent the
+ unconditional execution of one of the clauses.
+
+ """
+ def __init__(self, body):
+ self.body = body
+ self.lineno = body[0].lineno
+
+
+# TODO: some add_arcs methods here don't add arcs, they return them. Rename them.
+# TODO: the cause messages have too many commas.
+# TODO: Shouldn't the cause messages join with "and" instead of "or"?
+
+class AstArcAnalyzer(object):
+ """Analyze source text with an AST to find executable code paths."""
+
+ @contract(text='unicode', statements=set)
+ 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.multiline = multiline
+
+ if AST_DUMP: # pragma: debugging
+ # Dump the AST so that failing tests have helpful output.
+ print("Statements: {}".format(self.statements))
+ print("Multiline map: {}".format(self.multiline))
+ ast_dump(self.root_node)
+
+ self.arcs = set()
+
+ # A map from arc pairs to a list of pairs of sentence fragments:
+ # { (start, end): [(startmsg, endmsg), ...], }
+ #
+ # For an arc from line 17, they should be usable like:
+ # "Line 17 {endmsg}, because {startmsg}"
+ self.missing_arc_fragments = collections.defaultdict(list)
+ self.block_stack = []
+
+ # $set_env.py: COVERAGE_TRACK_ARCS - Trace every arc added while parsing code.
+ self.debug = bool(int(os.environ.get("COVERAGE_TRACK_ARCS", 0)))
+
+ def analyze(self):
+ """Examine the AST tree from `root_node` to determine possible arcs.
+
+ This sets the `arcs` attribute to be a set of (from, to) line number
+ pairs.
+
+ """
+ for node in ast.walk(self.root_node):
+ node_name = node.__class__.__name__
+ code_object_handler = getattr(self, "_code_object__" + node_name, None)
+ if code_object_handler is not None:
+ code_object_handler(node)
+
+ @contract(start=int, end=int)
+ def add_arc(self, start, end, smsg=None, emsg=None):
+ """Add an arc, including message fragments to use if it is missing."""
+ if self.debug: # pragma: debugging
+ print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg))
+ print(short_stack(limit=6))
+ self.arcs.add((start, end))
+
+ if smsg is not None or emsg is not None:
+ self.missing_arc_fragments[(start, end)].append((smsg, emsg))
+
+ def nearest_blocks(self):
+ """Yield the blocks in nearest-to-farthest order."""
+ return reversed(self.block_stack)
+
+ @contract(returns=int)
+ def line_for_node(self, node):
+ """What is the right line number to use for this node?
+
+ This dispatches to _line__Node functions where needed.
+
+ """
+ node_name = node.__class__.__name__
+ handler = getattr(self, "_line__" + node_name, None)
+ if handler is not None:
+ return handler(node)
+ else:
+ return node.lineno
+
+ def _line_decorated(self, node):
+ """Compute first line number for things that can be decorated (classes and functions)."""
+ lineno = node.lineno
+ if env.PYBEHAVIOR.trace_decorated_def:
+ if node.decorator_list:
+ lineno = node.decorator_list[0].lineno
+ return lineno
+
+ def _line__Assign(self, node):
+ return self.line_for_node(node.value)
+
+ _line__ClassDef = _line_decorated
+
+ def _line__Dict(self, node):
+ # Python 3.5 changed how dict literals are made.
+ if env.PYVERSION >= (3, 5) and node.keys:
+ if node.keys[0] is not None:
+ return node.keys[0].lineno
+ else:
+ # Unpacked dict literals `{**{'a':1}}` have None as the key,
+ # use the value in that case.
+ return node.values[0].lineno
+ else:
+ return node.lineno
+
+ _line__FunctionDef = _line_decorated
+ _line__AsyncFunctionDef = _line_decorated
+
+ def _line__List(self, node):
+ if node.elts:
+ return self.line_for_node(node.elts[0])
+ else:
+ return node.lineno
+
+ def _line__Module(self, node):
+ if 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([
+ "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global",
+ "Import", "ImportFrom", "Nonlocal", "Pass", "Print",
+ ])
+
+ @contract(returns='ArcStarts')
+ def add_arcs(self, node):
+ """Add the arcs for `node`.
+
+ Return a set of ArcStarts, exits from this node to the next. Because a
+ node represents an entire sub-tree (including its children), the exits
+ from a node can be arbitrarily complex::
+
+ if something(1):
+ if other(2):
+ doit(3)
+ else:
+ doit(5)
+
+ There are two exits from line 1: they start at line 3 and line 5.
+
+ """
+ node_name = node.__class__.__name__
+ handler = getattr(self, "_handle__" + node_name, None)
+ if handler is not None:
+ return handler(node)
+ else:
+ # No handler: either it's something that's ok to default (a simple
+ # statement), or it's something we overlooked. Change this 0 to 1
+ # to see if it's overlooked.
+ if 0:
+ if node_name not in self.OK_TO_DEFAULT:
+ print("*** Unhandled: {}".format(node))
+
+ # Default for simple statements: one exit from this node.
+ return set([ArcStart(self.line_for_node(node))])
+
+ @one_of("from_start, prev_starts")
+ @contract(returns='ArcStarts')
+ def add_body_arcs(self, body, from_start=None, prev_starts=None):
+ """Add arcs for the body of a compound statement.
+
+ `body` is the body node. `from_start` is a single `ArcStart` that can
+ be the previous line in flow before this body. `prev_starts` is a set
+ of ArcStarts that can be the previous line. Only one of them should be
+ given.
+
+ Returns a set of ArcStarts, the exits from this body.
+
+ """
+ if prev_starts is None:
+ prev_starts = set([from_start])
+ for body_node in body:
+ lineno = self.line_for_node(body_node)
+ first_line = self.multiline.get(lineno, lineno)
+ if first_line not in self.statements:
+ body_node = self.find_non_missing_node(body_node)
+ if body_node is None:
+ continue
+ lineno = self.line_for_node(body_node)
+ for prev_start in prev_starts:
+ self.add_arc(prev_start.lineno, lineno, prev_start.cause)
+ prev_starts = self.add_arcs(body_node)
+ return prev_starts
+
+ def find_non_missing_node(self, node):
+ """Search `node` looking for a child that has not been optimized away.
+
+ This might return the node you started with, or it will work recursively
+ to find a child node in self.statements.
+
+ Returns a node, or None if none of the node remains.
+
+ """
+ # This repeats work just done in add_body_arcs, but this duplication
+ # means we can avoid a function call in the 99.9999% case of not
+ # optimizing away statements.
+ lineno = self.line_for_node(node)
+ first_line = self.multiline.get(lineno, lineno)
+ if first_line in self.statements:
+ return node
+
+ missing_fn = getattr(self, "_missing__" + node.__class__.__name__, None)
+ if missing_fn:
+ node = missing_fn(node)
+ else:
+ node = None
+ return node
+
+ # Missing nodes: _missing__*
+ #
+ # Entire statements can be optimized away by Python. They will appear in
+ # the AST, but not the bytecode. These functions are called (by
+ # find_non_missing_node) to find a node to use instead of the missing
+ # node. They can return None if the node should truly be gone.
+
+ def _missing__If(self, node):
+ # If the if-node is missing, then one of its children might still be
+ # here, but not both. So return the first of the two that isn't missing.
+ # Use a NodeList to hold the clauses as a single node.
+ non_missing = self.find_non_missing_node(NodeList(node.body))
+ if non_missing:
+ return non_missing
+ if node.orelse:
+ return self.find_non_missing_node(NodeList(node.orelse))
+ return None
+
+ def _missing__NodeList(self, node):
+ # A NodeList might be a mixture of missing and present nodes. Find the
+ # ones that are present.
+ non_missing_children = []
+ for child in node.body:
+ child = self.find_non_missing_node(child)
+ if child is not None:
+ non_missing_children.append(child)
+
+ # Return the simplest representation of the present children.
+ if not non_missing_children:
+ return None
+ if len(non_missing_children) == 1:
+ return non_missing_children[0]
+ return NodeList(non_missing_children)
+
+ def _missing__While(self, node):
+ body_nodes = self.find_non_missing_node(NodeList(node.body))
+ if not body_nodes:
+ return None
+ # Make a synthetic While-true node.
+ new_while = ast.While()
+ new_while.lineno = body_nodes.lineno
+ new_while.test = ast.Name()
+ new_while.test.lineno = body_nodes.lineno
+ new_while.test.id = "True"
+ new_while.body = body_nodes.body
+ new_while.orelse = None
+ return new_while
+
+ def is_constant_expr(self, node):
+ """Is this a compile-time constant?"""
+ node_name = node.__class__.__name__
+ if node_name in ["Constant", "NameConstant", "Num"]:
+ return "Num"
+ elif node_name == "Name":
+ if node.id in ["True", "False", "None", "__debug__"]:
+ return "Name"
+ return None
+
+ # In the fullness of time, these might be good tests to write:
+ # while EXPR:
+ # while False:
+ # listcomps hidden deep in other expressions
+ # listcomps hidden in lists: x = [[i for i in range(10)]]
+ # nested function definitions
+
+
+ # Exit processing: process_*_exits
+ #
+ # These functions process the four kinds of jump exits: break, continue,
+ # raise, and return. To figure out where an exit goes, we have to look at
+ # the block stack context. For example, a break will jump to the nearest
+ # enclosing loop block, or the nearest enclosing finally block, whichever
+ # is nearer.
+
+ @contract(exits='ArcStarts')
+ def process_break_exits(self, exits):
+ """Add arcs due to jumps from `exits` being breaks."""
+ for block in self.nearest_blocks():
+ if isinstance(block, LoopBlock):
+ block.break_exits.update(exits)
+ break
+ elif isinstance(block, TryBlock) and block.final_start is not None:
+ block.break_from.update(exits)
+ break
+
+ @contract(exits='ArcStarts')
+ def process_continue_exits(self, exits):
+ """Add arcs due to jumps from `exits` being continues."""
+ for block in self.nearest_blocks():
+ if isinstance(block, LoopBlock):
+ for xit in exits:
+ self.add_arc(xit.lineno, block.start, xit.cause)
+ break
+ elif isinstance(block, TryBlock) and block.final_start is not None:
+ block.continue_from.update(exits)
+ break
+
+ @contract(exits='ArcStarts')
+ def process_raise_exits(self, exits):
+ """Add arcs due to jumps from `exits` being raises."""
+ for block in self.nearest_blocks():
+ if isinstance(block, TryBlock):
+ if block.handler_start is not None:
+ for xit in exits:
+ self.add_arc(xit.lineno, block.handler_start, xit.cause)
+ break
+ elif block.final_start is not None:
+ block.raise_from.update(exits)
+ break
+ elif isinstance(block, FunctionBlock):
+ for xit in exits:
+ self.add_arc(
+ xit.lineno, -block.start, xit.cause,
+ "didn't except from function {!r}".format(block.name),
+ )
+ break
+
+ @contract(exits='ArcStarts')
+ def process_return_exits(self, exits):
+ """Add arcs due to jumps from `exits` being returns."""
+ for block in self.nearest_blocks():
+ if isinstance(block, TryBlock) and block.final_start is not None:
+ block.return_from.update(exits)
+ break
+ elif isinstance(block, FunctionBlock):
+ for xit in exits:
+ self.add_arc(
+ xit.lineno, -block.start, xit.cause,
+ "didn't return from function {!r}".format(block.name),
+ )
+ break
+
+
+ # Handlers: _handle__*
+ #
+ # Each handler deals with a specific AST node type, dispatched from
+ # add_arcs. Handlers return the set of exits from that node, and can
+ # also call self.add_arc to record arcs they find. These functions mirror
+ # the Python semantics of each syntactic construct. See the docstring
+ # for add_arcs to understand the concept of exits from a node.
+
+ @contract(returns='ArcStarts')
+ def _handle__Break(self, node):
+ here = self.line_for_node(node)
+ break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed")
+ self.process_break_exits([break_start])
+ return set()
+
+ @contract(returns='ArcStarts')
+ def _handle_decorated(self, node):
+ """Add arcs for things that can be decorated (classes and functions)."""
+ main_line = last = node.lineno
+ if node.decorator_list:
+ if env.PYBEHAVIOR.trace_decorated_def:
+ last = None
+ for dec_node in node.decorator_list:
+ dec_start = self.line_for_node(dec_node)
+ if last is not None and dec_start != last:
+ self.add_arc(last, dec_start)
+ last = dec_start
+ if env.PYBEHAVIOR.trace_decorated_def:
+ self.add_arc(last, main_line)
+ last = main_line
+ # The definition line may have been missed, but we should have it
+ # in `self.statements`. For some constructs, `line_for_node` is
+ # not what we'd think of as the first line in the statement, so map
+ # it to the first one.
+ if node.body:
+ body_start = self.line_for_node(node.body[0])
+ body_start = self.multiline.get(body_start, body_start)
+ for lineno in range(last+1, body_start):
+ if lineno in self.statements:
+ self.add_arc(last, lineno)
+ last = lineno
+ # The body is handled in collect_arcs.
+ return set([ArcStart(last)])
+
+ _handle__ClassDef = _handle_decorated
+
+ @contract(returns='ArcStarts')
+ def _handle__Continue(self, node):
+ here = self.line_for_node(node)
+ continue_start = ArcStart(here, cause="the continue on line {lineno} wasn't executed")
+ self.process_continue_exits([continue_start])
+ return set()
+
+ @contract(returns='ArcStarts')
+ def _handle__For(self, node):
+ start = self.line_for_node(node.iter)
+ self.block_stack.append(LoopBlock(start=start))
+ from_start = ArcStart(start, cause="the loop on line {lineno} never started")
+ exits = self.add_body_arcs(node.body, from_start=from_start)
+ # Any exit from the body will go back to the top of the loop.
+ for xit in exits:
+ self.add_arc(xit.lineno, start, xit.cause)
+ my_block = self.block_stack.pop()
+ exits = my_block.break_exits
+ from_start = ArcStart(start, cause="the loop on line {lineno} didn't complete")
+ if node.orelse:
+ else_exits = self.add_body_arcs(node.orelse, from_start=from_start)
+ exits |= else_exits
+ else:
+ # No else clause: exit from the for line.
+ exits.add(from_start)
+ return exits
+
+ _handle__AsyncFor = _handle__For
+
+ _handle__FunctionDef = _handle_decorated
+ _handle__AsyncFunctionDef = _handle_decorated
+
+ @contract(returns='ArcStarts')
+ def _handle__If(self, node):
+ start = self.line_for_node(node.test)
+ from_start = ArcStart(start, cause="the condition on line {lineno} was never true")
+ exits = self.add_body_arcs(node.body, from_start=from_start)
+ from_start = ArcStart(start, cause="the condition on line {lineno} was never false")
+ exits |= self.add_body_arcs(node.orelse, from_start=from_start)
+ return exits
+
+ @contract(returns='ArcStarts')
+ def _handle__NodeList(self, node):
+ start = self.line_for_node(node)
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
+ return exits
+
+ @contract(returns='ArcStarts')
+ def _handle__Raise(self, node):
+ here = self.line_for_node(node)
+ raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed")
+ self.process_raise_exits([raise_start])
+ # `raise` statement jumps away, no exits from here.
+ return set()
+
+ @contract(returns='ArcStarts')
+ def _handle__Return(self, node):
+ here = self.line_for_node(node)
+ return_start = ArcStart(here, cause="the return on line {lineno} wasn't executed")
+ self.process_return_exits([return_start])
+ # `return` statement jumps away, no exits from here.
+ return set()
+
+ @contract(returns='ArcStarts')
+ def _handle__Try(self, node):
+ if node.handlers:
+ handler_start = self.line_for_node(node.handlers[0])
+ else:
+ handler_start = None
+
+ if node.finalbody:
+ final_start = self.line_for_node(node.finalbody[0])
+ else:
+ final_start = None
+
+ try_block = TryBlock(handler_start, final_start)
+ self.block_stack.append(try_block)
+
+ start = self.line_for_node(node)
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
+
+ # We're done with the `try` body, so this block no longer handles
+ # exceptions. We keep the block so the `finally` clause can pick up
+ # flows from the handlers and `else` clause.
+ if node.finalbody:
+ try_block.handler_start = None
+ if node.handlers:
+ # 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([])
+ else:
+ self.block_stack.pop()
+
+ handler_exits = set()
+
+ if node.handlers:
+ last_handler_start = None
+ for handler_node in node.handlers:
+ handler_start = self.line_for_node(handler_node)
+ if last_handler_start is not None:
+ self.add_arc(last_handler_start, handler_start)
+ last_handler_start = handler_start
+ from_cause = "the exception caught by line {lineno} didn't happen"
+ from_start = ArcStart(handler_start, cause=from_cause)
+ handler_exits |= self.add_body_arcs(handler_node.body, from_start=from_start)
+
+ if node.orelse:
+ exits = self.add_body_arcs(node.orelse, prev_starts=exits)
+
+ exits |= handler_exits
+
+ if node.finalbody:
+ self.block_stack.pop()
+ final_from = ( # You can get to the `finally` clause from:
+ exits | # the exits of the body or `else` clause,
+ try_block.break_from | # or a `break`,
+ try_block.continue_from | # or a `continue`,
+ try_block.raise_from | # or a `raise`,
+ try_block.return_from # or a `return`.
+ )
+
+ final_exits = self.add_body_arcs(node.finalbody, prev_starts=final_from)
+
+ if try_block.break_from:
+ if env.PYBEHAVIOR.finally_jumps_back:
+ for break_line in try_block.break_from:
+ lineno = break_line.lineno
+ cause = break_line.cause.format(lineno=lineno)
+ for final_exit in final_exits:
+ self.add_arc(final_exit.lineno, lineno, cause)
+ breaks = try_block.break_from
+ else:
+ breaks = self._combine_finally_starts(try_block.break_from, final_exits)
+ self.process_break_exits(breaks)
+
+ if try_block.continue_from:
+ if env.PYBEHAVIOR.finally_jumps_back:
+ for continue_line in try_block.continue_from:
+ lineno = continue_line.lineno
+ cause = continue_line.cause.format(lineno=lineno)
+ for final_exit in final_exits:
+ self.add_arc(final_exit.lineno, lineno, cause)
+ continues = try_block.continue_from
+ else:
+ continues = self._combine_finally_starts(try_block.continue_from, final_exits)
+ self.process_continue_exits(continues)
+
+ if try_block.raise_from:
+ self.process_raise_exits(
+ self._combine_finally_starts(try_block.raise_from, final_exits)
+ )
+
+ if try_block.return_from:
+ if env.PYBEHAVIOR.finally_jumps_back:
+ for return_line in try_block.return_from:
+ lineno = return_line.lineno
+ cause = return_line.cause.format(lineno=lineno)
+ for final_exit in final_exits:
+ self.add_arc(final_exit.lineno, lineno, cause)
+ returns = try_block.return_from
+ else:
+ returns = self._combine_finally_starts(try_block.return_from, final_exits)
+ self.process_return_exits(returns)
+
+ if exits:
+ # The finally clause's exits are only exits for the try block
+ # as a whole if the try block had some exits to begin with.
+ exits = final_exits
+
+ return exits
+
+ @contract(starts='ArcStarts', exits='ArcStarts', returns='ArcStarts')
+ def _combine_finally_starts(self, starts, exits):
+ """Helper for building the cause of `finally` branches.
+
+ "finally" clauses might not execute their exits, and the causes could
+ be due to a failure to execute any of the exits in the try block. So
+ we use the causes from `starts` as the causes for `exits`.
+ """
+ causes = []
+ for start in sorted(starts):
+ 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)
+ return exits
+
+ @contract(returns='ArcStarts')
+ def _handle__TryExcept(self, node):
+ # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get
+ # TryExcept, it means there was no finally, so fake it, and treat as
+ # a general Try node.
+ node.finalbody = []
+ return self._handle__Try(node)
+
+ @contract(returns='ArcStarts')
+ def _handle__TryFinally(self, node):
+ # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get
+ # TryFinally, see if there's a TryExcept nested inside. If so, merge
+ # them. Otherwise, fake fields to complete a Try node.
+ node.handlers = []
+ node.orelse = []
+
+ first = node.body[0]
+ if first.__class__.__name__ == "TryExcept" and node.lineno == first.lineno:
+ assert len(node.body) == 1
+ node.body = first.body
+ node.handlers = first.handlers
+ node.orelse = first.orelse
+
+ return self._handle__Try(node)
+
+ @contract(returns='ArcStarts')
+ def _handle__While(self, node):
+ constant_test = self.is_constant_expr(node.test)
+ start = to_top = self.line_for_node(node.test)
+ if constant_test and (env.PY3 or constant_test == "Num"):
+ 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")
+ exits = self.add_body_arcs(node.body, from_start=from_start)
+ for xit in exits:
+ self.add_arc(xit.lineno, to_top, xit.cause)
+ exits = set()
+ my_block = self.block_stack.pop()
+ exits.update(my_block.break_exits)
+ from_start = ArcStart(start, cause="the condition on line {lineno} was never false")
+ if node.orelse:
+ else_exits = self.add_body_arcs(node.orelse, from_start=from_start)
+ exits |= else_exits
+ else:
+ # No `else` clause: you can exit from the start.
+ if not constant_test:
+ exits.add(from_start)
+ return exits
+
+ @contract(returns='ArcStarts')
+ def _handle__With(self, node):
+ start = self.line_for_node(node)
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
+ return exits
+
+ _handle__AsyncWith = _handle__With
+
+ def _code_object__Module(self, node):
+ start = self.line_for_node(node)
+ if node.body:
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(-start))
+ for xit in exits:
+ self.add_arc(xit.lineno, -start, xit.cause, "didn't exit the module")
+ else:
+ # Empty module.
+ self.add_arc(-start, start)
+ self.add_arc(start, -start)
+
+ def _code_object__FunctionDef(self, node):
+ start = self.line_for_node(node)
+ self.block_stack.append(FunctionBlock(start=start, name=node.name))
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(-start))
+ self.process_return_exits(exits)
+ self.block_stack.pop()
+
+ _code_object__AsyncFunctionDef = _code_object__FunctionDef
+
+ def _code_object__ClassDef(self, node):
+ start = self.line_for_node(node)
+ self.add_arc(-start, start)
+ exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
+ for xit in exits:
+ self.add_arc(
+ xit.lineno, -start, xit.cause,
+ "didn't exit the body of class {!r}".format(node.name),
+ )
+
+ def _make_oneline_code_method(noun): # pylint: disable=no-self-argument
+ """A function to make methods for online callable _code_object__ methods."""
+ def _code_object__oneline_callable(self, node):
+ start = self.line_for_node(node)
+ self.add_arc(-start, start, None, "didn't run the {} on line {}".format(noun, start))
+ self.add_arc(
+ start, -start, None,
+ "didn't finish the {} on line {}".format(noun, start),
+ )
+ return _code_object__oneline_callable
+
+ _code_object__Lambda = _make_oneline_code_method("lambda")
+ _code_object__GeneratorExp = _make_oneline_code_method("generator expression")
+ _code_object__DictComp = _make_oneline_code_method("dictionary comprehension")
+ _code_object__SetComp = _make_oneline_code_method("set comprehension")
+ if env.PY3:
+ _code_object__ListComp = _make_oneline_code_method("list comprehension")
+
+
+if AST_DUMP: # pragma: debugging
+ # Code only used when dumping the AST for debugging.
+
+ SKIP_DUMP_FIELDS = ["ctx"]
+
+ def _is_simple_value(value):
+ """Is `value` simple enough to be displayed on a single line?"""
+ return (
+ value in [None, [], (), {}, set()] or
+ isinstance(value, (string_class, int, float))
+ )
+
+ def ast_dump(node, depth=0):
+ """Dump the AST for `node`.
+
+ This recursively walks the AST, printing a readable version.
+
+ """
+ indent = " " * depth
+ if not isinstance(node, ast.AST):
+ print("{}<{} {!r}>".format(indent, node.__class__.__name__, node))
+ return
+
+ lineno = getattr(node, "lineno", None)
+ if lineno is not None:
+ linemark = " @ {}".format(node.lineno)
+ else:
+ linemark = ""
+ head = "{}<{}{}".format(indent, node.__class__.__name__, linemark)
+
+ named_fields = [
+ (name, value)
+ for name, value in ast.iter_fields(node)
+ if name not in SKIP_DUMP_FIELDS
+ ]
+ if not named_fields:
+ print("{}>".format(head))
+ elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]):
+ field_name, value = named_fields[0]
+ print("{} {}: {!r}>".format(head, field_name, value))
+ else:
+ print(head)
+ if 0:
+ print("{}# mro: {}".format(
+ indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]),
+ ))
+ next_indent = indent + " "
+ for field_name, value in named_fields:
+ prefix = "{}{}:".format(next_indent, field_name)
+ if _is_simple_value(value):
+ print("{} {!r}".format(prefix, value))
+ elif isinstance(value, list):
+ print("{} [".format(prefix))
+ for n in value:
+ ast_dump(n, depth + 8)
+ print("{}]".format(next_indent))
+ else:
+ print(prefix)
+ ast_dump(value, depth + 8)
+
+ print("{}>".format(indent))
diff --git a/third_party/python/coverage/coverage/phystokens.py b/third_party/python/coverage/coverage/phystokens.py
new file mode 100644
index 0000000000..b6866e7dd0
--- /dev/null
+++ b/third_party/python/coverage/coverage/phystokens.py
@@ -0,0 +1,297 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Better tokenizing for coverage.py."""
+
+import codecs
+import keyword
+import re
+import sys
+import token
+import tokenize
+
+from coverage import env
+from coverage.backward import iternext, unicode_class
+from coverage.misc import contract
+
+
+def phys_tokens(toks):
+ """Return all physical tokens, even line continuations.
+
+ tokenize.generate_tokens() doesn't return a token for the backslash that
+ continues lines. This wrapper provides those tokens so that we can
+ re-create a faithful representation of the original source.
+
+ Returns the same values as generate_tokens()
+
+ """
+ last_line = None
+ last_lineno = -1
+ last_ttext = None
+ for ttype, ttext, (slineno, scol), (elineno, ecol), ltext in toks:
+ if last_lineno != elineno:
+ if last_line and last_line.endswith("\\\n"):
+ # We are at the beginning of a new line, and the last line
+ # ended with a backslash. We probably have to inject a
+ # backslash token into the stream. Unfortunately, there's more
+ # to figure out. This code::
+ #
+ # usage = """\
+ # HEY THERE
+ # """
+ #
+ # triggers this condition, but the token text is::
+ #
+ # '"""\\\nHEY THERE\n"""'
+ #
+ # so we need to figure out if the backslash is already in the
+ # string token or not.
+ inject_backslash = True
+ if last_ttext.endswith("\\"):
+ inject_backslash = False
+ elif ttype == token.STRING:
+ if "\n" in ttext and ttext.split('\n', 1)[0][-1] == '\\':
+ # It's a multi-line string and the first line ends with
+ # a backslash, so we don't need to inject another.
+ inject_backslash = False
+ if inject_backslash:
+ # Figure out what column the backslash is in.
+ ccol = len(last_line.split("\n")[-2]) - 1
+ # Yield the token, with a fake token type.
+ yield (
+ 99999, "\\\n",
+ (slineno, ccol), (slineno, ccol+2),
+ last_line
+ )
+ last_line = ltext
+ if ttype not in (tokenize.NEWLINE, tokenize.NL):
+ last_ttext = ttext
+ yield ttype, ttext, (slineno, scol), (elineno, ecol), ltext
+ last_lineno = elineno
+
+
+@contract(source='unicode')
+def source_token_lines(source):
+ """Generate a series of lines, one for each line in `source`.
+
+ Each line is a list of pairs, each pair is a token::
+
+ [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
+
+ Each pair has a token class, and the token text.
+
+ If you concatenate all the token texts, and then join them with newlines,
+ you should have your original `source` back, with two differences:
+ trailing whitespace is not preserved, and a final line with no newline
+ is indistinguishable from a final line with a newline.
+
+ """
+
+ ws_tokens = set([token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL])
+ line = []
+ col = 0
+
+ source = source.expandtabs(8).replace('\r\n', '\n')
+ tokgen = generate_tokens(source)
+
+ for ttype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen):
+ mark_start = True
+ for part in re.split('(\n)', ttext):
+ if part == '\n':
+ yield line
+ line = []
+ col = 0
+ mark_end = False
+ elif part == '':
+ mark_end = False
+ elif ttype in ws_tokens:
+ mark_end = False
+ else:
+ if mark_start and scol > col:
+ line.append(("ws", u" " * (scol - col)))
+ mark_start = False
+ tok_class = tokenize.tok_name.get(ttype, 'xx').lower()[:3]
+ if ttype == token.NAME and keyword.iskeyword(ttext):
+ tok_class = "key"
+ line.append((tok_class, part))
+ mark_end = True
+ scol = 0
+ if mark_end:
+ col = ecol
+
+ if line:
+ yield line
+
+
+class CachedTokenizer(object):
+ """A one-element cache around tokenize.generate_tokens.
+
+ When reporting, coverage.py tokenizes files twice, once to find the
+ structure of the file, and once to syntax-color it. Tokenizing is
+ expensive, and easily cached.
+
+ This is a one-element cache so that our twice-in-a-row tokenizing doesn't
+ actually tokenize twice.
+
+ """
+ def __init__(self):
+ self.last_text = None
+ self.last_tokens = None
+
+ @contract(text='unicode')
+ def generate_tokens(self, text):
+ """A stand-in for `tokenize.generate_tokens`."""
+ if text != self.last_text:
+ self.last_text = text
+ readline = iternext(text.splitlines(True))
+ self.last_tokens = list(tokenize.generate_tokens(readline))
+ return self.last_tokens
+
+# Create our generate_tokens cache as a callable replacement function.
+generate_tokens = CachedTokenizer().generate_tokens
+
+
+COOKIE_RE = re.compile(r"^[ \t]*#.*coding[:=][ \t]*([-\w.]+)", flags=re.MULTILINE)
+
+@contract(source='bytes')
+def _source_encoding_py2(source):
+ """Determine the encoding for `source`, according to PEP 263.
+
+ `source` is a byte string, the text of the program.
+
+ Returns a string, the name of the encoding.
+
+ """
+ assert isinstance(source, bytes)
+
+ # Do this so the detect_encode code we copied will work.
+ readline = iternext(source.splitlines(True))
+
+ # This is mostly code adapted from Py3.2's tokenize module.
+
+ def _get_normal_name(orig_enc):
+ """Imitates get_normal_name in tokenizer.c."""
+ # Only care about the first 12 characters.
+ enc = orig_enc[:12].lower().replace("_", "-")
+ if re.match(r"^utf-8($|-)", enc):
+ return "utf-8"
+ if re.match(r"^(latin-1|iso-8859-1|iso-latin-1)($|-)", enc):
+ return "iso-8859-1"
+ return orig_enc
+
+ # From detect_encode():
+ # It detects the encoding from the presence of a UTF-8 BOM or an encoding
+ # cookie as specified in PEP-0263. If both a BOM and a cookie are present,
+ # but disagree, a SyntaxError will be raised. If the encoding cookie is an
+ # invalid charset, raise a SyntaxError. Note that if a UTF-8 BOM is found,
+ # 'utf-8-sig' is returned.
+
+ # If no encoding is specified, then the default will be returned.
+ default = 'ascii'
+
+ bom_found = False
+ encoding = None
+
+ def read_or_stop():
+ """Get the next source line, or ''."""
+ try:
+ return readline()
+ except StopIteration:
+ return ''
+
+ def find_cookie(line):
+ """Find an encoding cookie in `line`."""
+ try:
+ line_string = line.decode('ascii')
+ except UnicodeDecodeError:
+ return None
+
+ matches = COOKIE_RE.findall(line_string)
+ if not matches:
+ return None
+ encoding = _get_normal_name(matches[0])
+ try:
+ codec = codecs.lookup(encoding)
+ except LookupError:
+ # This behavior mimics the Python interpreter
+ raise SyntaxError("unknown encoding: " + encoding)
+
+ if bom_found:
+ # codecs in 2.3 were raw tuples of functions, assume the best.
+ codec_name = getattr(codec, 'name', encoding)
+ if codec_name != 'utf-8':
+ # This behavior mimics the Python interpreter
+ raise SyntaxError('encoding problem: utf-8')
+ encoding += '-sig'
+ return encoding
+
+ first = read_or_stop()
+ if first.startswith(codecs.BOM_UTF8):
+ bom_found = True
+ first = first[3:]
+ default = 'utf-8-sig'
+ if not first:
+ return default
+
+ encoding = find_cookie(first)
+ if encoding:
+ return encoding
+
+ second = read_or_stop()
+ if not second:
+ return default
+
+ encoding = find_cookie(second)
+ if encoding:
+ return encoding
+
+ return default
+
+
+@contract(source='bytes')
+def _source_encoding_py3(source):
+ """Determine the encoding for `source`, according to PEP 263.
+
+ `source` is a byte string: the text of the program.
+
+ Returns a string, the name of the encoding.
+
+ """
+ readline = iternext(source.splitlines(True))
+ return tokenize.detect_encoding(readline)[0]
+
+
+if env.PY3:
+ source_encoding = _source_encoding_py3
+else:
+ source_encoding = _source_encoding_py2
+
+
+@contract(source='unicode')
+def compile_unicode(source, filename, mode):
+ """Just like the `compile` builtin, but works on any Unicode string.
+
+ Python 2's compile() builtin has a stupid restriction: if the source string
+ is Unicode, then it may not have a encoding declaration in it. Why not?
+ Who knows! It also decodes to utf8, and then tries to interpret those utf8
+ bytes according to the encoding declaration. Why? Who knows!
+
+ This function neuters the coding declaration, and compiles it.
+
+ """
+ source = neuter_encoding_declaration(source)
+ if env.PY2 and isinstance(filename, unicode_class):
+ filename = filename.encode(sys.getfilesystemencoding(), "replace")
+ code = compile(source, filename, mode)
+ return code
+
+
+@contract(source='unicode', returns='unicode')
+def neuter_encoding_declaration(source):
+ """Return `source`, with any encoding declaration neutered."""
+ if COOKIE_RE.search(source):
+ source_lines = source.splitlines(True)
+ for lineno in range(min(2, len(source_lines))):
+ source_lines[lineno] = COOKIE_RE.sub("# (deleted declaration)", source_lines[lineno])
+ source = "".join(source_lines)
+ return source
diff --git a/third_party/python/coverage/coverage/plugin.py b/third_party/python/coverage/coverage/plugin.py
new file mode 100644
index 0000000000..6997b489bb
--- /dev/null
+++ b/third_party/python/coverage/coverage/plugin.py
@@ -0,0 +1,533 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""
+.. versionadded:: 4.0
+
+Plug-in interfaces for coverage.py.
+
+Coverage.py supports a few different kinds of plug-ins that change its
+behavior:
+
+* File tracers implement tracing of non-Python file types.
+
+* Configurers add custom configuration, using Python code to change the
+ configuration.
+
+* Dynamic context switchers decide when the dynamic context has changed, for
+ example, to record what test function produced the coverage.
+
+To write a coverage.py plug-in, create a module with a subclass of
+:class:`~coverage.CoveragePlugin`. You will override methods in your class to
+participate in various aspects of coverage.py's processing.
+Different types of plug-ins have to override different methods.
+
+Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info`
+to provide debugging information about their operation.
+
+Your module must also contain a ``coverage_init`` function that registers an
+instance of your plug-in class::
+
+ import coverage
+
+ class MyPlugin(coverage.CoveragePlugin):
+ ...
+
+ def coverage_init(reg, options):
+ reg.add_file_tracer(MyPlugin())
+
+You use the `reg` parameter passed to your ``coverage_init`` function to
+register your plug-in object. The registration method you call depends on
+what kind of plug-in it is.
+
+If your plug-in takes options, the `options` parameter is a dictionary of your
+plug-in's options from the coverage.py configuration file. Use them however
+you want to configure your object before registering it.
+
+Coverage.py will store its own information on your plug-in object, using
+attributes whose names start with ``_coverage_``. Don't be startled.
+
+.. warning::
+ Plug-ins are imported by coverage.py before it begins measuring code.
+ If you write a plugin in your own project, it might import your product
+ code before coverage.py can start measuring. This can result in your
+ own code being reported as missing.
+
+ One solution is to put your plugins in your project tree, but not in
+ your importable Python package.
+
+
+.. _file_tracer_plugins:
+
+File Tracers
+============
+
+File tracers implement measurement support for non-Python files. File tracers
+implement the :meth:`~coverage.CoveragePlugin.file_tracer` method to claim
+files and the :meth:`~coverage.CoveragePlugin.file_reporter` method to report
+on those files.
+
+In your ``coverage_init`` function, use the ``add_file_tracer`` method to
+register your file tracer.
+
+
+.. _configurer_plugins:
+
+Configurers
+===========
+
+.. versionadded:: 4.5
+
+Configurers modify the configuration of coverage.py during start-up.
+Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to
+change the configuration.
+
+In your ``coverage_init`` function, use the ``add_configurer`` method to
+register your configurer.
+
+
+.. _dynamic_context_plugins:
+
+Dynamic Context Switchers
+=========================
+
+.. versionadded:: 5.0
+
+Dynamic context switcher plugins implement the
+:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute
+the context label for each measured frame.
+
+Computed context labels are useful when you want to group measured data without
+modifying the source code.
+
+For example, you could write a plugin that checks `frame.f_code` to inspect
+the currently executed method, and set the context label to a fully qualified
+method name if it's an instance method of `unittest.TestCase` and the method
+name starts with 'test'. Such a plugin would provide basic coverage grouping
+by test and could be used with test runners that have no built-in coveragepy
+support.
+
+In your ``coverage_init`` function, use the ``add_dynamic_context`` method to
+register your dynamic context switcher.
+
+"""
+
+from coverage import files
+from coverage.misc import contract, _needs_to_implement
+
+
+class CoveragePlugin(object):
+ """Base class for coverage.py plug-ins."""
+
+ def file_tracer(self, filename): # pylint: disable=unused-argument
+ """Get a :class:`FileTracer` object for a file.
+
+ Plug-in type: file tracer.
+
+ Every Python source file is offered to your plug-in to give it a chance
+ to take responsibility for tracing the file. If your plug-in can
+ handle the file, it should return a :class:`FileTracer` object.
+ Otherwise return None.
+
+ There is no way to register your plug-in for particular files.
+ Instead, this method is invoked for all files as they are executed,
+ and the plug-in decides whether it can trace the file or not.
+ Be prepared for `filename` to refer to all kinds of files that have
+ nothing to do with your plug-in.
+
+ The file name will be a Python file being executed. There are two
+ broad categories of behavior for a plug-in, depending on the kind of
+ files your plug-in supports:
+
+ * Static file names: each of your original source files has been
+ converted into a distinct Python file. Your plug-in is invoked with
+ the Python file name, and it maps it back to its original source
+ file.
+
+ * Dynamic file names: all of your source files are executed by the same
+ Python file. In this case, your plug-in implements
+ :meth:`FileTracer.dynamic_source_filename` to provide the actual
+ source file for each execution frame.
+
+ `filename` is a string, the path to the file being considered. This is
+ the absolute real path to the file. If you are comparing to other
+ paths, be sure to take this into account.
+
+ Returns a :class:`FileTracer` object to use to trace `filename`, or
+ None if this plug-in cannot trace this file.
+
+ """
+ return None
+
+ def file_reporter(self, filename): # pylint: disable=unused-argument
+ """Get the :class:`FileReporter` class to use for a file.
+
+ Plug-in type: file tracer.
+
+ This will only be invoked if `filename` returns non-None from
+ :meth:`file_tracer`. It's an error to return None from this method.
+
+ Returns a :class:`FileReporter` object to use to report on `filename`,
+ or the string `"python"` to have coverage.py treat the file as Python.
+
+ """
+ _needs_to_implement(self, "file_reporter")
+
+ def dynamic_context(self, frame): # pylint: disable=unused-argument
+ """Get the dynamically computed context label for `frame`.
+
+ Plug-in type: dynamic context.
+
+ This method is invoked for each frame when outside of a dynamic
+ context, to see if a new dynamic context should be started. If it
+ returns a string, a new context label is set for this and deeper
+ frames. The dynamic context ends when this frame returns.
+
+ Returns a string to start a new dynamic context, or None if no new
+ context should be started.
+
+ """
+ return None
+
+ def find_executable_files(self, src_dir): # pylint: disable=unused-argument
+ """Yield all of the executable files in `src_dir`, recursively.
+
+ Plug-in type: file tracer.
+
+ Executability is a plug-in-specific property, but generally means files
+ which would have been considered for coverage analysis, had they been
+ included automatically.
+
+ Returns or yields a sequence of strings, the paths to files that could
+ have been executed, including files that had been executed.
+
+ """
+ return []
+
+ def configure(self, config):
+ """Modify the configuration of coverage.py.
+
+ Plug-in type: configurer.
+
+ This method is called during coverage.py start-up, to give your plug-in
+ a chance to change the configuration. The `config` parameter is an
+ object with :meth:`~coverage.Coverage.get_option` and
+ :meth:`~coverage.Coverage.set_option` methods. Do not call any other
+ methods on the `config` object.
+
+ """
+ pass
+
+ def sys_info(self):
+ """Get a list of information useful for debugging.
+
+ Plug-in type: any.
+
+ This method will be invoked for ``--debug=sys``. Your
+ plug-in can return any information it wants to be displayed.
+
+ Returns a list of pairs: `[(name, value), ...]`.
+
+ """
+ return []
+
+
+class FileTracer(object):
+ """Support needed for files during the execution phase.
+
+ File tracer plug-ins implement subclasses of FileTracer to return from
+ their :meth:`~CoveragePlugin.file_tracer` method.
+
+ You may construct this object from :meth:`CoveragePlugin.file_tracer` any
+ way you like. A natural choice would be to pass the file name given to
+ `file_tracer`.
+
+ `FileTracer` objects should only be created in the
+ :meth:`CoveragePlugin.file_tracer` method.
+
+ See :ref:`howitworks` for details of the different coverage.py phases.
+
+ """
+
+ def source_filename(self):
+ """The source file name for this file.
+
+ This may be any file name you like. A key responsibility of a plug-in
+ is to own the mapping from Python execution back to whatever source
+ file name was originally the source of the code.
+
+ See :meth:`CoveragePlugin.file_tracer` for details about static and
+ dynamic file names.
+
+ Returns the file name to credit with this execution.
+
+ """
+ _needs_to_implement(self, "source_filename")
+
+ def has_dynamic_source_filename(self):
+ """Does this FileTracer have dynamic source file names?
+
+ FileTracers can provide dynamically determined file names by
+ implementing :meth:`dynamic_source_filename`. Invoking that function
+ is expensive. To determine whether to invoke it, coverage.py uses the
+ result of this function to know if it needs to bother invoking
+ :meth:`dynamic_source_filename`.
+
+ See :meth:`CoveragePlugin.file_tracer` for details about static and
+ dynamic file names.
+
+ Returns True if :meth:`dynamic_source_filename` should be called to get
+ dynamic source file names.
+
+ """
+ return False
+
+ def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument
+ """Get a dynamically computed source file name.
+
+ Some plug-ins need to compute the source file name dynamically for each
+ frame.
+
+ This function will not be invoked if
+ :meth:`has_dynamic_source_filename` returns False.
+
+ Returns the source file name for this frame, or None if this frame
+ shouldn't be measured.
+
+ """
+ return None
+
+ def line_number_range(self, frame):
+ """Get the range of source line numbers for a given a call frame.
+
+ The call frame is examined, and the source line number in the original
+ file is returned. The return value is a pair of numbers, the starting
+ line number and the ending line number, both inclusive. For example,
+ returning (5, 7) means that lines 5, 6, and 7 should be considered
+ executed.
+
+ This function might decide that the frame doesn't indicate any lines
+ from the source file were executed. Return (-1, -1) in this case to
+ tell coverage.py that no lines should be recorded for this frame.
+
+ """
+ lineno = frame.f_lineno
+ return lineno, lineno
+
+
+class FileReporter(object):
+ """Support needed for files during the analysis and reporting phases.
+
+ File tracer plug-ins implement a subclass of `FileReporter`, and return
+ instances from their :meth:`CoveragePlugin.file_reporter` method.
+
+ There are many methods here, but only :meth:`lines` is required, to provide
+ the set of executable lines in the file.
+
+ See :ref:`howitworks` for details of the different coverage.py phases.
+
+ """
+
+ def __init__(self, filename):
+ """Simple initialization of a `FileReporter`.
+
+ The `filename` argument is the path to the file being reported. This
+ will be available as the `.filename` attribute on the object. Other
+ method implementations on this base class rely on this attribute.
+
+ """
+ self.filename = filename
+
+ def __repr__(self):
+ return "<{0.__class__.__name__} filename={0.filename!r}>".format(self)
+
+ def relative_filename(self):
+ """Get the relative file name for this file.
+
+ This file path will be displayed in reports. The default
+ implementation will supply the actual project-relative file path. You
+ only need to supply this method if you have an unusual syntax for file
+ paths.
+
+ """
+ return files.relative_filename(self.filename)
+
+ @contract(returns='unicode')
+ def source(self):
+ """Get the source for the file.
+
+ Returns a Unicode string.
+
+ The base implementation simply reads the `self.filename` file and
+ decodes it as UTF8. Override this method if your file isn't readable
+ as a text file, or if you need other encoding support.
+
+ """
+ with open(self.filename, "rb") as f:
+ return f.read().decode("utf8")
+
+ def lines(self):
+ """Get the executable lines in this file.
+
+ Your plug-in must determine which lines in the file were possibly
+ executable. This method returns a set of those line numbers.
+
+ Returns a set of line numbers.
+
+ """
+ _needs_to_implement(self, "lines")
+
+ def excluded_lines(self):
+ """Get the excluded executable lines in this file.
+
+ Your plug-in can use any method it likes to allow the user to exclude
+ executable lines from consideration.
+
+ Returns a set of line numbers.
+
+ The base implementation returns the empty set.
+
+ """
+ return set()
+
+ def translate_lines(self, lines):
+ """Translate recorded lines into reported lines.
+
+ Some file formats will want to report lines slightly differently than
+ they are recorded. For example, Python records the last line of a
+ multi-line statement, but reports are nicer if they mention the first
+ line.
+
+ Your plug-in can optionally define this method to perform these kinds
+ of adjustment.
+
+ `lines` is a sequence of integers, the recorded line numbers.
+
+ Returns a set of integers, the adjusted line numbers.
+
+ The base implementation returns the numbers unchanged.
+
+ """
+ return set(lines)
+
+ def arcs(self):
+ """Get the executable arcs in this file.
+
+ To support branch coverage, your plug-in needs to be able to indicate
+ possible execution paths, as a set of line number pairs. Each pair is
+ a `(prev, next)` pair indicating that execution can transition from the
+ `prev` line number to the `next` line number.
+
+ Returns a set of pairs of line numbers. The default implementation
+ returns an empty set.
+
+ """
+ return set()
+
+ def no_branch_lines(self):
+ """Get the lines excused from branch coverage in this file.
+
+ Your plug-in can use any method it likes to allow the user to exclude
+ lines from consideration of branch coverage.
+
+ Returns a set of line numbers.
+
+ The base implementation returns the empty set.
+
+ """
+ return set()
+
+ def translate_arcs(self, arcs):
+ """Translate recorded arcs into reported arcs.
+
+ Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of
+ line number pairs.
+
+ Returns a set of line number pairs.
+
+ The default implementation returns `arcs` unchanged.
+
+ """
+ return arcs
+
+ def exit_counts(self):
+ """Get a count of exits from that each line.
+
+ To determine which lines are branches, coverage.py looks for lines that
+ have more than one exit. This function creates a dict mapping each
+ executable line number to a count of how many exits it has.
+
+ To be honest, this feels wrong, and should be refactored. Let me know
+ if you attempt to implement this method in your plug-in...
+
+ """
+ return {}
+
+ def missing_arc_description(self, start, end, executed_arcs=None): # pylint: disable=unused-argument
+ """Provide an English sentence describing a missing arc.
+
+ The `start` and `end` arguments are the line numbers of the missing
+ arc. Negative numbers indicate entering or exiting code objects.
+
+ The `executed_arcs` argument is a set of line number pairs, the arcs
+ that were executed in this file.
+
+ By default, this simply returns the string "Line {start} didn't jump
+ to {end}".
+
+ """
+ return "Line {start} didn't jump to line {end}".format(start=start, end=end)
+
+ def source_token_lines(self):
+ """Generate a series of tokenized lines, one for each line in `source`.
+
+ These tokens are used for syntax-colored reports.
+
+ Each line is a list of pairs, each pair is a token::
+
+ [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
+
+ Each pair has a token class, and the token text. The token classes
+ are:
+
+ * ``'com'``: a comment
+ * ``'key'``: a keyword
+ * ``'nam'``: a name, or identifier
+ * ``'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
+ newlines, you should have your original source back.
+
+ The default implementation simply returns each line tagged as
+ ``'txt'``.
+
+ """
+ for line in self.source().splitlines():
+ yield [('txt', line)]
+
+ # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
+ # of them defined.
+
+ def __eq__(self, other):
+ return isinstance(other, FileReporter) and self.filename == other.filename
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __lt__(self, other):
+ return self.filename < other.filename
+
+ def __le__(self, other):
+ return self.filename <= other.filename
+
+ def __gt__(self, other):
+ return self.filename > other.filename
+
+ def __ge__(self, other):
+ return self.filename >= other.filename
+
+ __hash__ = None # This object doesn't need to be hashed.
diff --git a/third_party/python/coverage/coverage/plugin_support.py b/third_party/python/coverage/coverage/plugin_support.py
new file mode 100644
index 0000000000..89c1c7658f
--- /dev/null
+++ b/third_party/python/coverage/coverage/plugin_support.py
@@ -0,0 +1,281 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Support for plugins."""
+
+import os
+import os.path
+import sys
+
+from coverage.misc import CoverageException, isolate_module
+from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
+
+os = isolate_module(os)
+
+
+class Plugins(object):
+ """The currently loaded collection of coverage.py plugins."""
+
+ def __init__(self):
+ self.order = []
+ self.names = {}
+ self.file_tracers = []
+ self.configurers = []
+ self.context_switchers = []
+
+ self.current_module = None
+ self.debug = None
+
+ @classmethod
+ def load_plugins(cls, modules, config, debug=None):
+ """Load plugins from `modules`.
+
+ Returns a Plugins object with the loaded and configured plugins.
+
+ """
+ plugins = cls()
+ plugins.debug = debug
+
+ for module in modules:
+ plugins.current_module = module
+ __import__(module)
+ mod = sys.modules[module]
+
+ coverage_init = getattr(mod, "coverage_init", None)
+ if not coverage_init:
+ raise CoverageException(
+ "Plugin module %r didn't define a coverage_init function" % module
+ )
+
+ options = config.get_plugin_options(module)
+ coverage_init(plugins, options)
+
+ plugins.current_module = None
+ return plugins
+
+ def add_file_tracer(self, plugin):
+ """Add a file tracer plugin.
+
+ `plugin` is an instance of a third-party plugin class. It must
+ implement the :meth:`CoveragePlugin.file_tracer` method.
+
+ """
+ self._add_plugin(plugin, self.file_tracers)
+
+ def add_configurer(self, plugin):
+ """Add a configuring plugin.
+
+ `plugin` is an instance of a third-party plugin class. It must
+ implement the :meth:`CoveragePlugin.configure` method.
+
+ """
+ self._add_plugin(plugin, self.configurers)
+
+ def add_dynamic_context(self, plugin):
+ """Add a dynamic context plugin.
+
+ `plugin` is an instance of a third-party plugin class. It must
+ implement the :meth:`CoveragePlugin.dynamic_context` method.
+
+ """
+ self._add_plugin(plugin, self.context_switchers)
+
+ def add_noop(self, plugin):
+ """Add a plugin that does nothing.
+
+ This is only useful for testing the plugin support.
+
+ """
+ self._add_plugin(plugin, None)
+
+ def _add_plugin(self, plugin, specialized):
+ """Add a plugin object.
+
+ `plugin` is a :class:`CoveragePlugin` instance to add. `specialized`
+ is a list to append the plugin to.
+
+ """
+ plugin_name = "%s.%s" % (self.current_module, plugin.__class__.__name__)
+ if self.debug and self.debug.should('plugin'):
+ self.debug.write("Loaded plugin %r: %r" % (self.current_module, plugin))
+ labelled = LabelledDebug("plugin %r" % (self.current_module,), self.debug)
+ plugin = DebugPluginWrapper(plugin, labelled)
+
+ # pylint: disable=attribute-defined-outside-init
+ plugin._coverage_plugin_name = plugin_name
+ plugin._coverage_enabled = True
+ self.order.append(plugin)
+ self.names[plugin_name] = plugin
+ if specialized is not None:
+ specialized.append(plugin)
+
+ def __nonzero__(self):
+ return bool(self.order)
+
+ __bool__ = __nonzero__
+
+ def __iter__(self):
+ return iter(self.order)
+
+ def get(self, plugin_name):
+ """Return a plugin by name."""
+ return self.names[plugin_name]
+
+
+class LabelledDebug(object):
+ """A Debug writer, but with labels for prepending to the messages."""
+
+ def __init__(self, label, debug, prev_labels=()):
+ self.labels = list(prev_labels) + [label]
+ self.debug = debug
+
+ def add_label(self, label):
+ """Add a label to the writer, and return a new `LabelledDebug`."""
+ return LabelledDebug(label, self.debug, self.labels)
+
+ def message_prefix(self):
+ """The prefix to use on messages, combining the labels."""
+ prefixes = self.labels + ['']
+ return ":\n".join(" "*i+label for i, label in enumerate(prefixes))
+
+ def write(self, message):
+ """Write `message`, but with the labels prepended."""
+ self.debug.write("%s%s" % (self.message_prefix(), message))
+
+
+class DebugPluginWrapper(CoveragePlugin):
+ """Wrap a plugin, and use debug to report on what it's doing."""
+
+ def __init__(self, plugin, debug):
+ super(DebugPluginWrapper, self).__init__()
+ self.plugin = plugin
+ self.debug = debug
+
+ def file_tracer(self, filename):
+ tracer = self.plugin.file_tracer(filename)
+ self.debug.write("file_tracer(%r) --> %r" % (filename, tracer))
+ if tracer:
+ debug = self.debug.add_label("file %r" % (filename,))
+ tracer = DebugFileTracerWrapper(tracer, debug)
+ return tracer
+
+ def file_reporter(self, filename):
+ reporter = self.plugin.file_reporter(filename)
+ self.debug.write("file_reporter(%r) --> %r" % (filename, reporter))
+ if reporter:
+ debug = self.debug.add_label("file %r" % (filename,))
+ reporter = DebugFileReporterWrapper(filename, reporter, debug)
+ return reporter
+
+ def dynamic_context(self, frame):
+ context = self.plugin.dynamic_context(frame)
+ self.debug.write("dynamic_context(%r) --> %r" % (frame, context))
+ return context
+
+ def find_executable_files(self, src_dir):
+ executable_files = self.plugin.find_executable_files(src_dir)
+ self.debug.write("find_executable_files(%r) --> %r" % (src_dir, executable_files))
+ return executable_files
+
+ def configure(self, config):
+ self.debug.write("configure(%r)" % (config,))
+ self.plugin.configure(config)
+
+ def sys_info(self):
+ return self.plugin.sys_info()
+
+
+class DebugFileTracerWrapper(FileTracer):
+ """A debugging `FileTracer`."""
+
+ def __init__(self, tracer, debug):
+ self.tracer = tracer
+ self.debug = debug
+
+ def _show_frame(self, frame):
+ """A short string identifying a frame, for debug messages."""
+ return "%s@%d" % (
+ os.path.basename(frame.f_code.co_filename),
+ frame.f_lineno,
+ )
+
+ def source_filename(self):
+ sfilename = self.tracer.source_filename()
+ self.debug.write("source_filename() --> %r" % (sfilename,))
+ return sfilename
+
+ def has_dynamic_source_filename(self):
+ has = self.tracer.has_dynamic_source_filename()
+ self.debug.write("has_dynamic_source_filename() --> %r" % (has,))
+ return has
+
+ def dynamic_source_filename(self, filename, frame):
+ dyn = self.tracer.dynamic_source_filename(filename, frame)
+ self.debug.write("dynamic_source_filename(%r, %s) --> %r" % (
+ filename, self._show_frame(frame), dyn,
+ ))
+ return dyn
+
+ def line_number_range(self, frame):
+ pair = self.tracer.line_number_range(frame)
+ self.debug.write("line_number_range(%s) --> %r" % (self._show_frame(frame), pair))
+ return pair
+
+
+class DebugFileReporterWrapper(FileReporter):
+ """A debugging `FileReporter`."""
+
+ def __init__(self, filename, reporter, debug):
+ super(DebugFileReporterWrapper, self).__init__(filename)
+ self.reporter = reporter
+ self.debug = debug
+
+ def relative_filename(self):
+ ret = self.reporter.relative_filename()
+ self.debug.write("relative_filename() --> %r" % (ret,))
+ return ret
+
+ def lines(self):
+ ret = self.reporter.lines()
+ self.debug.write("lines() --> %r" % (ret,))
+ return ret
+
+ def excluded_lines(self):
+ ret = self.reporter.excluded_lines()
+ self.debug.write("excluded_lines() --> %r" % (ret,))
+ return ret
+
+ def translate_lines(self, lines):
+ ret = self.reporter.translate_lines(lines)
+ self.debug.write("translate_lines(%r) --> %r" % (lines, ret))
+ return ret
+
+ def translate_arcs(self, arcs):
+ ret = self.reporter.translate_arcs(arcs)
+ self.debug.write("translate_arcs(%r) --> %r" % (arcs, ret))
+ return ret
+
+ def no_branch_lines(self):
+ ret = self.reporter.no_branch_lines()
+ self.debug.write("no_branch_lines() --> %r" % (ret,))
+ return ret
+
+ def exit_counts(self):
+ ret = self.reporter.exit_counts()
+ self.debug.write("exit_counts() --> %r" % (ret,))
+ return ret
+
+ def arcs(self):
+ ret = self.reporter.arcs()
+ self.debug.write("arcs() --> %r" % (ret,))
+ return ret
+
+ def source(self):
+ ret = self.reporter.source()
+ self.debug.write("source() --> %d chars" % (len(ret),))
+ return ret
+
+ def source_token_lines(self):
+ ret = list(self.reporter.source_token_lines())
+ self.debug.write("source_token_lines() --> %d tokens" % (len(ret),))
+ return ret
diff --git a/third_party/python/coverage/coverage/python.py b/third_party/python/coverage/coverage/python.py
new file mode 100644
index 0000000000..81aa66ba16
--- /dev/null
+++ b/third_party/python/coverage/coverage/python.py
@@ -0,0 +1,249 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Python source expertise for coverage.py"""
+
+import os.path
+import types
+import zipimport
+
+from coverage import env, files
+from coverage.misc import contract, expensive, isolate_module, join_regex
+from coverage.misc import CoverageException, NoSource
+from coverage.parser import PythonParser
+from coverage.phystokens import source_token_lines, source_encoding
+from coverage.plugin import FileReporter
+
+os = isolate_module(os)
+
+
+@contract(returns='bytes')
+def read_python_source(filename):
+ """Read the Python source text from `filename`.
+
+ Returns bytes.
+
+ """
+ with open(filename, "rb") as f:
+ source = f.read()
+
+ if env.IRONPYTHON:
+ # IronPython reads Unicode strings even for "rb" files.
+ source = bytes(source)
+
+ return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
+
+
+@contract(returns='unicode')
+def get_python_source(filename):
+ """Return the source code, as unicode."""
+ base, ext = os.path.splitext(filename)
+ if ext == ".py" and env.WINDOWS:
+ exts = [".py", ".pyw"]
+ else:
+ exts = [ext]
+
+ for ext in exts:
+ try_filename = base + ext
+ if os.path.exists(try_filename):
+ # A regular text file: open it.
+ source = read_python_source(try_filename)
+ break
+
+ # Maybe it's in a zip file?
+ source = get_zip_bytes(try_filename)
+ if source is not None:
+ break
+ else:
+ # Couldn't find source.
+ exc_msg = "No source for code: '%s'.\n" % (filename,)
+ exc_msg += "Aborting report output, consider using -i."
+ raise NoSource(exc_msg)
+
+ # Replace \f because of http://bugs.python.org/issue19035
+ source = source.replace(b'\f', b' ')
+ source = source.decode(source_encoding(source), "replace")
+
+ # Python code should always end with a line with a newline.
+ if source and source[-1] != '\n':
+ source += '\n'
+
+ return source
+
+
+@contract(returns='bytes|None')
+def get_zip_bytes(filename):
+ """Get data from `filename` if it is a zip file path.
+
+ Returns the bytestring data read from the zip file, or None if no zip file
+ could be found or `filename` isn't in it. The data returned will be
+ an empty string if the file is empty.
+
+ """
+ markers = ['.zip'+os.sep, '.egg'+os.sep, '.pex'+os.sep]
+ for marker in markers:
+ if marker in filename:
+ parts = filename.split(marker)
+ try:
+ zi = zipimport.zipimporter(parts[0]+marker[:-1])
+ except zipimport.ZipImportError:
+ continue
+ try:
+ data = zi.get_data(parts[1])
+ except IOError:
+ continue
+ return data
+ return None
+
+
+def source_for_file(filename):
+ """Return the source filename for `filename`.
+
+ Given a file name being traced, return the best guess as to the source
+ file to attribute it to.
+
+ """
+ if filename.endswith(".py"):
+ # .py files are themselves source files.
+ return filename
+
+ elif filename.endswith((".pyc", ".pyo")):
+ # Bytecode files probably have source files near them.
+ py_filename = filename[:-1]
+ if os.path.exists(py_filename):
+ # Found a .py file, use that.
+ return py_filename
+ if env.WINDOWS:
+ # On Windows, it could be a .pyw file.
+ pyw_filename = py_filename + "w"
+ if os.path.exists(pyw_filename):
+ return pyw_filename
+ # Didn't find source, but it's probably the .py file we want.
+ return py_filename
+
+ elif filename.endswith("$py.class"):
+ # Jython is easy to guess.
+ return filename[:-9] + ".py"
+
+ # No idea, just use the file name as-is.
+ return filename
+
+
+def source_for_morf(morf):
+ """Get the source filename for the module-or-file `morf`."""
+ if hasattr(morf, '__file__') and morf.__file__:
+ filename = morf.__file__
+ elif isinstance(morf, types.ModuleType):
+ # A module should have had .__file__, otherwise we can't use it.
+ # This could be a PEP-420 namespace package.
+ raise CoverageException("Module {} has no file".format(morf))
+ else:
+ filename = morf
+
+ filename = source_for_file(files.unicode_filename(filename))
+ return filename
+
+
+class PythonFileReporter(FileReporter):
+ """Report support for a Python file."""
+
+ def __init__(self, morf, coverage=None):
+ self.coverage = coverage
+
+ filename = source_for_morf(morf)
+
+ super(PythonFileReporter, self).__init__(files.canonical_filename(filename))
+
+ if hasattr(morf, '__name__'):
+ name = morf.__name__.replace(".", os.sep)
+ if os.path.basename(filename).startswith('__init__.'):
+ name += os.sep + "__init__"
+ name += ".py"
+ name = files.unicode_filename(name)
+ else:
+ name = files.relative_filename(filename)
+ self.relname = name
+
+ self._source = None
+ self._parser = None
+ self._excluded = None
+
+ def __repr__(self):
+ return "<PythonFileReporter {!r}>".format(self.filename)
+
+ @contract(returns='unicode')
+ def relative_filename(self):
+ return self.relname
+
+ @property
+ def parser(self):
+ """Lazily create a :class:`PythonParser`."""
+ if self._parser is None:
+ self._parser = PythonParser(
+ filename=self.filename,
+ exclude=self.coverage._exclude_regex('exclude'),
+ )
+ self._parser.parse_source()
+ return self._parser
+
+ def lines(self):
+ """Return the line numbers of statements in the file."""
+ return self.parser.statements
+
+ def excluded_lines(self):
+ """Return the line numbers of statements in the file."""
+ return self.parser.excluded
+
+ def translate_lines(self, lines):
+ return self.parser.translate_lines(lines)
+
+ def translate_arcs(self, arcs):
+ return self.parser.translate_arcs(arcs)
+
+ @expensive
+ def no_branch_lines(self):
+ no_branch = self.parser.lines_matching(
+ join_regex(self.coverage.config.partial_list),
+ join_regex(self.coverage.config.partial_always_list)
+ )
+ return no_branch
+
+ @expensive
+ def arcs(self):
+ return self.parser.arcs()
+
+ @expensive
+ def exit_counts(self):
+ return self.parser.exit_counts()
+
+ def missing_arc_description(self, start, end, executed_arcs=None):
+ return self.parser.missing_arc_description(start, end, executed_arcs)
+
+ @contract(returns='unicode')
+ def source(self):
+ if self._source is None:
+ self._source = get_python_source(self.filename)
+ return self._source
+
+ def should_be_python(self):
+ """Does it seem like this file should contain Python?
+
+ This is used to decide if a file reported as part of the execution of
+ a program was really likely to have contained Python in the first
+ place.
+
+ """
+ # Get the file extension.
+ _, ext = os.path.splitext(self.filename)
+
+ # Anything named *.py* should be Python.
+ if ext.startswith('.py'):
+ return True
+ # A file with no extension should be Python.
+ if not ext:
+ return True
+ # Everything else is probably not Python.
+ return False
+
+ def source_token_lines(self):
+ return source_token_lines(self.source())
diff --git a/third_party/python/coverage/coverage/pytracer.py b/third_party/python/coverage/coverage/pytracer.py
new file mode 100644
index 0000000000..44bfc8d6a8
--- /dev/null
+++ b/third_party/python/coverage/coverage/pytracer.py
@@ -0,0 +1,245 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Raw data collector for coverage.py."""
+
+import atexit
+import dis
+import sys
+
+from coverage import env
+
+# We need the YIELD_VALUE opcode below, in a comparison-friendly form.
+YIELD_VALUE = dis.opmap['YIELD_VALUE']
+if env.PY2:
+ YIELD_VALUE = chr(YIELD_VALUE)
+
+
+class PyTracer(object):
+ """Python implementation of the raw data tracer."""
+
+ # Because of poor implementations of trace-function-manipulating tools,
+ # the Python trace function must be kept very simple. In particular, there
+ # must be only one function ever set as the trace function, both through
+ # sys.settrace, and as the return value from the trace function. Put
+ # another way, the trace function must always return itself. It cannot
+ # swap in other functions, or return None to avoid tracing a particular
+ # frame.
+ #
+ # The trace manipulator that introduced this restriction is DecoratorTools,
+ # which sets a trace function, and then later restores the pre-existing one
+ # by calling sys.settrace with a function it found in the current frame.
+ #
+ # Systems that use DecoratorTools (or similar trace manipulations) must use
+ # PyTracer to get accurate results. The command-line --timid argument is
+ # used to force the use of this tracer.
+
+ def __init__(self):
+ # Attributes set from the collector:
+ self.data = None
+ self.trace_arcs = False
+ self.should_trace = None
+ self.should_trace_cache = None
+ self.should_start_context = None
+ self.warn = None
+ # The threading module to use, if any.
+ self.threading = None
+
+ self.cur_file_dict = None
+ self.last_line = 0 # int, but uninitialized.
+ self.cur_file_name = None
+ self.context = None
+ self.started_context = False
+
+ self.data_stack = []
+ self.last_exc_back = None
+ self.last_exc_firstlineno = 0
+ self.thread = None
+ self.stopped = False
+ self._activity = False
+
+ self.in_atexit = False
+ # On exit, self.in_atexit = True
+ atexit.register(setattr, self, 'in_atexit', True)
+
+ def __repr__(self):
+ return "<PyTracer at {}: {} lines in {} files>".format(
+ id(self),
+ sum(len(v) for v in self.data.values()),
+ len(self.data),
+ )
+
+ 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(
+ marker,
+ id(self),
+ self.thread.ident,
+ len(self.data_stack),
+ self.threading.currentThread().ident,
+ " ".join(map(str, args))
+ ))
+
+ 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 (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)
+ sys.settrace(None)
+ return None
+
+ if self.last_exc_back:
+ if frame == self.last_exc_back:
+ # Someone forgot a return event.
+ if self.trace_arcs and self.cur_file_dict:
+ pair = (self.last_line, -self.last_exc_firstlineno)
+ self.cur_file_dict[pair] = None
+ self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = (
+ self.data_stack.pop()
+ )
+ self.last_exc_back = None
+
+ 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)
+ if context_maybe is not None:
+ self.context = context_maybe
+ self.started_context = True
+ self.switch_context(self.context)
+ else:
+ self.started_context = False
+ else:
+ self.started_context = False
+
+ # Entering a new frame. Decide if we should trace
+ # in this file.
+ self._activity = True
+ self.data_stack.append(
+ (
+ self.cur_file_dict,
+ self.cur_file_name,
+ self.last_line,
+ self.started_context,
+ )
+ )
+ filename = frame.f_code.co_filename
+ self.cur_file_name = filename
+ disp = self.should_trace_cache.get(filename)
+ if disp is None:
+ 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:
+ 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
+ # line number for calls, and the real line number for generators.
+ if getattr(frame, 'f_lasti', -1) < 0:
+ self.last_line = -frame.f_code.co_firstlineno
+ else:
+ self.last_line = frame.f_lineno
+ elif event == 'line':
+ # 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:
+ self.cur_file_dict[lineno] = None
+ self.last_line = lineno
+ elif event == 'return':
+ if self.trace_arcs and self.cur_file_dict:
+ # Record an arc leaving the function, but beware that a
+ # "return" event might just mean yielding from a generator.
+ # Jython seems to have an empty co_code, so just assume return.
+ code = frame.f_code.co_code
+ if (not code) or code[frame.f_lasti] != YIELD_VALUE:
+ first = frame.f_code.co_firstlineno
+ self.cur_file_dict[(self.last_line, -first)] = None
+ # Leaving this function, pop the filename stack.
+ self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = (
+ self.data_stack.pop()
+ )
+ # Leaving a context?
+ if self.started_context:
+ self.context = None
+ self.switch_context(None)
+ elif event == 'exception':
+ self.last_exc_back = frame.f_back
+ self.last_exc_firstlineno = frame.f_code.co_firstlineno
+ return self._trace
+
+ def start(self):
+ """Start this Tracer.
+
+ Return a Python function suitable for use with sys.settrace().
+
+ """
+ self.stopped = False
+ if self.threading:
+ if self.thread is None:
+ self.thread = self.threading.currentThread()
+ else:
+ if self.thread.ident != self.threading.currentThread().ident:
+ # Re-starting from a different thread!? Don't set the trace
+ # function, but we are marked as running again, so maybe it
+ # will be ok?
+ #self.log("~", "starting on different threads")
+ return self._trace
+
+ sys.settrace(self._trace)
+ return self._trace
+
+ def stop(self):
+ """Stop this Tracer."""
+ # Get the active tracer callback before setting the stop flag to be
+ # able to detect if the tracer was changed prior to stopping it.
+ tf = sys.gettrace()
+
+ # Set the stop flag. The actual call to sys.settrace(None) will happen
+ # in the self._trace callback itself to make sure to call it from the
+ # right thread.
+ self.stopped = True
+
+ if self.threading and self.thread.ident != self.threading.currentThread().ident:
+ # Called on a different thread than started us: we can't unhook
+ # ourselves, but we've set the flag that we should stop, so we
+ # won't do any more tracing.
+ #self.log("~", "stopping on different threads")
+ return
+
+ if self.warn:
+ # PyPy clears the trace function before running atexit functions,
+ # so don't warn if we are in atexit on PyPy and the trace function
+ # 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(
+ "Trace function changed, measurement is likely wrong: %r" % (tf,),
+ slug="trace-changed",
+ )
+
+ def activity(self):
+ """Has there been any activity?"""
+ return self._activity
+
+ def reset_activity(self):
+ """Reset the activity() flag."""
+ self._activity = False
+
+ def get_stats(self):
+ """Return a dictionary of statistics, or None."""
+ return None
diff --git a/third_party/python/coverage/coverage/report.py b/third_party/python/coverage/coverage/report.py
new file mode 100644
index 0000000000..64678ff95d
--- /dev/null
+++ b/third_party/python/coverage/coverage/report.py
@@ -0,0 +1,86 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Reporter foundation for coverage.py."""
+import sys
+
+from coverage import env
+from coverage.files import prep_patterns, FnmatchMatcher
+from coverage.misc import CoverageException, NoSource, NotPython, ensure_dir_for_file, file_be_gone
+
+
+def render_report(output_path, reporter, morfs):
+ """Run the provided reporter ensuring any required setup and cleanup is done
+
+ At a high level this method ensures the output file is ready to be written to. Then writes the
+ report to it. Then closes the file and deletes any garbage created if necessary.
+ """
+ file_to_close = None
+ delete_file = False
+ if output_path:
+ if output_path == '-':
+ outfile = sys.stdout
+ else:
+ # Ensure that the output directory is created; done here
+ # because this report pre-opens the output file.
+ # HTMLReport does this using the Report plumbing because
+ # its task is more complex, being multiple files.
+ ensure_dir_for_file(output_path)
+ open_kwargs = {}
+ if env.PY3:
+ open_kwargs['encoding'] = 'utf8'
+ outfile = open(output_path, "w", **open_kwargs)
+ file_to_close = outfile
+ try:
+ return reporter.report(morfs, outfile=outfile)
+ except CoverageException:
+ delete_file = True
+ raise
+ finally:
+ if file_to_close:
+ file_to_close.close()
+ if delete_file:
+ file_be_gone(output_path)
+
+
+def get_analysis_to_report(coverage, morfs):
+ """Get the files to report on.
+
+ For each morf in `morfs`, if it should be reported on (based on the omit
+ and include configuration options), yield a pair, the `FileReporter` and
+ `Analysis` for the morf.
+
+ """
+ file_reporters = coverage._get_file_reporters(morfs)
+ config = coverage.config
+
+ if config.report_include:
+ matcher = FnmatchMatcher(prep_patterns(config.report_include))
+ file_reporters = [fr for fr in file_reporters if matcher.match(fr.filename)]
+
+ if config.report_omit:
+ matcher = FnmatchMatcher(prep_patterns(config.report_omit))
+ file_reporters = [fr for fr in file_reporters if not matcher.match(fr.filename)]
+
+ if not file_reporters:
+ raise CoverageException("No data to report.")
+
+ for fr in sorted(file_reporters):
+ try:
+ analysis = coverage._analyze(fr)
+ except NoSource:
+ if not config.ignore_errors:
+ raise
+ except NotPython:
+ # Only report errors for .py files, and only if we didn't
+ # explicitly suppress those errors.
+ # NotPython is only raised by PythonFileReporter, which has a
+ # should_be_python() method.
+ if fr.should_be_python():
+ if config.ignore_errors:
+ msg = "Couldn't parse Python file '{}'".format(fr.filename)
+ coverage._warn(msg, slug="couldnt-parse")
+ else:
+ raise
+ else:
+ yield (fr, analysis)
diff --git a/third_party/python/coverage/coverage/results.py b/third_party/python/coverage/coverage/results.py
new file mode 100644
index 0000000000..ae8366bf5a
--- /dev/null
+++ b/third_party/python/coverage/coverage/results.py
@@ -0,0 +1,346 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Results of coverage measurement."""
+
+import collections
+
+from coverage.backward import iitems
+from coverage.debug import SimpleReprMixin
+from coverage.misc import contract, CoverageException, nice_pair
+
+
+class Analysis(object):
+ """The results of analyzing a FileReporter."""
+
+ def __init__(self, data, file_reporter, file_mapper):
+ self.data = data
+ self.file_reporter = file_reporter
+ self.filename = file_mapper(self.file_reporter.filename)
+ self.statements = self.file_reporter.lines()
+ self.excluded = self.file_reporter.excluded_lines()
+
+ # Identify missing statements.
+ executed = self.data.lines(self.filename) or []
+ executed = self.file_reporter.translate_lines(executed)
+ self.executed = executed
+ self.missing = self.statements - self.executed
+
+ if self.data.has_arcs():
+ self._arc_possibilities = sorted(self.file_reporter.arcs())
+ self.exit_counts = self.file_reporter.exit_counts()
+ self.no_branch = self.file_reporter.no_branch_lines()
+ n_branches = self._total_branches()
+ mba = self.missing_branch_arcs()
+ n_partial_branches = sum(len(v) for k,v in iitems(mba) if k not in self.missing)
+ n_missing_branches = sum(len(v) for k,v in iitems(mba))
+ else:
+ self._arc_possibilities = []
+ self.exit_counts = {}
+ self.no_branch = set()
+ n_branches = n_partial_branches = n_missing_branches = 0
+
+ self.numbers = Numbers(
+ n_files=1,
+ n_statements=len(self.statements),
+ n_excluded=len(self.excluded),
+ n_missing=len(self.missing),
+ n_branches=n_branches,
+ n_partial_branches=n_partial_branches,
+ n_missing_branches=n_missing_branches,
+ )
+
+ def missing_formatted(self, branches=False):
+ """The missing line numbers, formatted nicely.
+
+ Returns a string like "1-2, 5-11, 13-14".
+
+ If `branches` is true, includes the missing branch arcs also.
+
+ """
+ if branches and self.has_arcs():
+ arcs = iitems(self.missing_branch_arcs())
+ else:
+ arcs = None
+
+ return format_lines(self.statements, self.missing, arcs=arcs)
+
+ def has_arcs(self):
+ """Were arcs measured in this result?"""
+ return self.data.has_arcs()
+
+ @contract(returns='list(tuple(int, int))')
+ def arc_possibilities(self):
+ """Returns a sorted list of the arcs in the code."""
+ return self._arc_possibilities
+
+ @contract(returns='list(tuple(int, int))')
+ def arcs_executed(self):
+ """Returns a sorted list of the arcs actually executed in the code."""
+ executed = self.data.arcs(self.filename) or []
+ executed = self.file_reporter.translate_arcs(executed)
+ return sorted(executed)
+
+ @contract(returns='list(tuple(int, int))')
+ def arcs_missing(self):
+ """Returns a sorted list of the arcs in the code not executed."""
+ possible = self.arc_possibilities()
+ executed = self.arcs_executed()
+ missing = (
+ p for p in possible
+ if p not in executed
+ and p[0] not in self.no_branch
+ )
+ return sorted(missing)
+
+ @contract(returns='list(tuple(int, int))')
+ def arcs_unpredicted(self):
+ """Returns a sorted list of the executed arcs missing from the code."""
+ possible = self.arc_possibilities()
+ executed = self.arcs_executed()
+ # Exclude arcs here which connect a line to itself. They can occur
+ # in executed data in some cases. This is where they can cause
+ # trouble, and here is where it's the least burden to remove them.
+ # Also, generators can somehow cause arcs from "enter" to "exit", so
+ # make sure we have at least one positive value.
+ unpredicted = (
+ e for e in executed
+ if e not in possible
+ and e[0] != e[1]
+ and (e[0] > 0 or e[1] > 0)
+ )
+ return sorted(unpredicted)
+
+ def _branch_lines(self):
+ """Returns a list of line numbers that have more than one exit."""
+ return [l1 for l1,count in iitems(self.exit_counts) if count > 1]
+
+ def _total_branches(self):
+ """How many total branches are there?"""
+ return sum(count for count in self.exit_counts.values() if count > 1)
+
+ @contract(returns='dict(int: list(int))')
+ def missing_branch_arcs(self):
+ """Return arcs that weren't executed from branch lines.
+
+ Returns {l1:[l2a,l2b,...], ...}
+
+ """
+ missing = self.arcs_missing()
+ branch_lines = set(self._branch_lines())
+ mba = collections.defaultdict(list)
+ for l1, l2 in missing:
+ if l1 in branch_lines:
+ mba[l1].append(l2)
+ return mba
+
+ @contract(returns='dict(int: tuple(int, int))')
+ def branch_stats(self):
+ """Get stats about branches.
+
+ Returns a dict mapping line numbers to a tuple:
+ (total_exits, taken_exits).
+ """
+
+ missing_arcs = self.missing_branch_arcs()
+ stats = {}
+ for lnum in self._branch_lines():
+ exits = self.exit_counts[lnum]
+ try:
+ missing = len(missing_arcs[lnum])
+ except KeyError:
+ missing = 0
+ stats[lnum] = (exits, exits - missing)
+ return stats
+
+
+class Numbers(SimpleReprMixin):
+ """The numerical results of measuring coverage.
+
+ This holds the basic statistics from `Analysis`, and is used to roll
+ up statistics across files.
+
+ """
+ # A global to determine the precision on coverage percentages, the number
+ # of decimal places.
+ _precision = 0
+ _near0 = 1.0 # These will change when _precision is changed.
+ _near100 = 99.0
+
+ def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
+ n_branches=0, n_partial_branches=0, n_missing_branches=0
+ ):
+ self.n_files = n_files
+ self.n_statements = n_statements
+ self.n_excluded = n_excluded
+ self.n_missing = n_missing
+ self.n_branches = n_branches
+ self.n_partial_branches = n_partial_branches
+ self.n_missing_branches = n_missing_branches
+
+ def init_args(self):
+ """Return a list for __init__(*args) to recreate this object."""
+ return [
+ self.n_files, self.n_statements, self.n_excluded, self.n_missing,
+ self.n_branches, self.n_partial_branches, self.n_missing_branches,
+ ]
+
+ @classmethod
+ def set_precision(cls, precision):
+ """Set the number of decimal places used to report percentages."""
+ assert 0 <= precision < 10
+ cls._precision = precision
+ cls._near0 = 1.0 / 10**precision
+ cls._near100 = 100.0 - cls._near0
+
+ @property
+ def n_executed(self):
+ """Returns the number of executed statements."""
+ return self.n_statements - self.n_missing
+
+ @property
+ def n_executed_branches(self):
+ """Returns the number of executed branches."""
+ return self.n_branches - self.n_missing_branches
+
+ @property
+ def pc_covered(self):
+ """Returns a single percentage value for coverage."""
+ if self.n_statements > 0:
+ numerator, denominator = self.ratio_covered
+ pc_cov = (100.0 * numerator) / denominator
+ else:
+ pc_cov = 100.0
+ return pc_cov
+
+ @property
+ def pc_covered_str(self):
+ """Returns the percent covered, as a string, without a percent sign.
+
+ Note that "0" is only returned when the value is truly zero, and "100"
+ is only returned when the value is truly 100. Rounding can never
+ result in either "0" or "100".
+
+ """
+ pc = self.pc_covered
+ if 0 < pc < self._near0:
+ pc = self._near0
+ elif self._near100 < pc < 100:
+ pc = self._near100
+ else:
+ pc = round(pc, self._precision)
+ return "%.*f" % (self._precision, pc)
+
+ @classmethod
+ def pc_str_width(cls):
+ """How many characters wide can pc_covered_str be?"""
+ width = 3 # "100"
+ if cls._precision > 0:
+ width += 1 + cls._precision
+ return width
+
+ @property
+ def ratio_covered(self):
+ """Return a numerator and denominator for the coverage ratio."""
+ numerator = self.n_executed + self.n_executed_branches
+ denominator = self.n_statements + self.n_branches
+ return numerator, denominator
+
+ def __add__(self, other):
+ nums = Numbers()
+ nums.n_files = self.n_files + other.n_files
+ nums.n_statements = self.n_statements + other.n_statements
+ nums.n_excluded = self.n_excluded + other.n_excluded
+ nums.n_missing = self.n_missing + other.n_missing
+ nums.n_branches = self.n_branches + other.n_branches
+ nums.n_partial_branches = (
+ self.n_partial_branches + other.n_partial_branches
+ )
+ nums.n_missing_branches = (
+ self.n_missing_branches + other.n_missing_branches
+ )
+ return nums
+
+ def __radd__(self, other):
+ # Implementing 0+Numbers allows us to sum() a list of Numbers.
+ if other == 0:
+ return self
+ return NotImplemented
+
+
+def _line_ranges(statements, lines):
+ """Produce a list of ranges for `format_lines`."""
+ statements = sorted(statements)
+ lines = sorted(lines)
+
+ pairs = []
+ start = None
+ lidx = 0
+ for stmt in statements:
+ if lidx >= len(lines):
+ break
+ if stmt == lines[lidx]:
+ lidx += 1
+ if not start:
+ start = stmt
+ end = stmt
+ elif start:
+ pairs.append((start, end))
+ start = None
+ if start:
+ pairs.append((start, end))
+ return pairs
+
+
+def format_lines(statements, lines, arcs=None):
+ """Nicely format a list of line numbers.
+
+ Format a list of line numbers for printing by coalescing groups of lines as
+ long as the lines represent consecutive statements. This will coalesce
+ even if there are gaps between statements.
+
+ For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
+ `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
+
+ Both `lines` and `statements` can be any iterable. All of the elements of
+ `lines` must be in `statements`, and all of the values must be positive
+ integers.
+
+ If `arcs` is provided, they are (start,[end,end,end]) pairs that will be
+ included in the output as long as start isn't in `lines`.
+
+ """
+ line_items = [(pair[0], nice_pair(pair)) for pair in _line_ranges(statements, lines)]
+ if arcs:
+ line_exits = sorted(arcs)
+ for line, exits in line_exits:
+ for ex in sorted(exits):
+ if line not in lines:
+ dest = (ex if ex > 0 else "exit")
+ line_items.append((line, "%d->%s" % (line, dest)))
+
+ ret = ', '.join(t[-1] for t in sorted(line_items))
+ return ret
+
+
+@contract(total='number', fail_under='number', precision=int, returns=bool)
+def should_fail_under(total, fail_under, precision):
+ """Determine if a total should fail due to fail-under.
+
+ `total` is a float, the coverage measurement total. `fail_under` is the
+ fail_under setting to compare with. `precision` is the number of digits
+ to consider after the decimal point.
+
+ Returns True if the total should fail.
+
+ """
+ # We can never achieve higher than 100% coverage, or less than zero.
+ if not (0 <= fail_under <= 100.0):
+ msg = "fail_under={} is invalid. Must be between 0 and 100.".format(fail_under)
+ raise CoverageException(msg)
+
+ # Special case for fail_under=100, it must really be 100.
+ if fail_under == 100.0 and total != 100.0:
+ return True
+
+ return round(total, precision) < fail_under
diff --git a/third_party/python/coverage/coverage/sqldata.py b/third_party/python/coverage/coverage/sqldata.py
new file mode 100644
index 0000000000..b8ee885327
--- /dev/null
+++ b/third_party/python/coverage/coverage/sqldata.py
@@ -0,0 +1,1106 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Sqlite coverage data."""
+
+# TODO: factor out dataop debugging to a wrapper class?
+# TODO: make sure all dataop debugging is in place somehow
+
+import collections
+import datetime
+import glob
+import itertools
+import os
+import re
+import sqlite3
+import sys
+import zlib
+
+from coverage import env
+from coverage.backward import get_thread_id, iitems, to_bytes, to_string
+from coverage.debug import NoDebugging, SimpleReprMixin, clipped_repr
+from coverage.files import PathAliases
+from coverage.misc import CoverageException, contract, file_be_gone, filename_suffix, isolate_module
+from coverage.numbits import numbits_to_nums, numbits_union, nums_to_numbits
+from coverage.version import __version__
+
+os = isolate_module(os)
+
+# If you change the schema, increment the SCHEMA_VERSION, and update the
+# docs in docs/dbschema.rst also.
+
+SCHEMA_VERSION = 7
+
+# Schema versions:
+# 1: Released in 5.0a2
+# 2: Added contexts in 5.0a3.
+# 3: Replaced line table with line_map table.
+# 4: Changed line_map.bitmap to line_map.numbits.
+# 5: Added foreign key declarations.
+# 6: Key-value in meta.
+# 7: line_map -> line_bits
+
+SCHEMA = """\
+CREATE TABLE coverage_schema (
+ -- One row, to record the version of the schema in this db.
+ version integer
+);
+
+CREATE TABLE meta (
+ -- Key-value pairs, to record metadata about the data
+ key text,
+ value text,
+ unique (key)
+ -- Keys:
+ -- 'has_arcs' boolean -- Is this data recording branches?
+ -- 'sys_argv' text -- The coverage command line that recorded the data.
+ -- 'version' text -- The version of coverage.py that made the file.
+ -- 'when' text -- Datetime when the file was created.
+);
+
+CREATE TABLE file (
+ -- A row per file measured.
+ id integer primary key,
+ path text,
+ unique (path)
+);
+
+CREATE TABLE context (
+ -- A row per context measured.
+ id integer primary key,
+ context text,
+ unique (context)
+);
+
+CREATE TABLE line_bits (
+ -- If recording lines, a row per context per file executed.
+ -- All of the line numbers for that file/context are in one numbits.
+ file_id integer, -- foreign key to `file`.
+ context_id integer, -- foreign key to `context`.
+ numbits blob, -- see the numbits functions in coverage.numbits
+ foreign key (file_id) references file (id),
+ foreign key (context_id) references context (id),
+ unique (file_id, context_id)
+);
+
+CREATE TABLE arc (
+ -- If recording branches, a row per context per from/to line transition executed.
+ file_id integer, -- foreign key to `file`.
+ context_id integer, -- foreign key to `context`.
+ fromno integer, -- line number jumped from.
+ tono integer, -- line number jumped to.
+ foreign key (file_id) references file (id),
+ foreign key (context_id) references context (id),
+ unique (file_id, context_id, fromno, tono)
+);
+
+CREATE TABLE tracer (
+ -- A row per file indicating the tracer used for that file.
+ file_id integer primary key,
+ tracer text,
+ foreign key (file_id) references file (id)
+);
+"""
+
+class CoverageData(SimpleReprMixin):
+ """Manages collected coverage data, including file storage.
+
+ This class is the public supported API to the data that coverage.py
+ collects during program execution. It includes information about what code
+ was executed. It does not include information from the analysis phase, to
+ determine what lines could have been executed, or what lines were not
+ executed.
+
+ .. note::
+
+ The data file is currently a SQLite database file, with a
+ :ref:`documented schema <dbschema>`. The schema is subject to change
+ though, so be careful about querying it directly. Use this API if you
+ can to isolate yourself from changes.
+
+ There are a number of kinds of data that can be collected:
+
+ * **lines**: the line numbers of source lines that were executed.
+ These are always available.
+
+ * **arcs**: pairs of source and destination line numbers for transitions
+ between source lines. These are only available if branch coverage was
+ used.
+
+ * **file tracer names**: the module names of the file tracer plugins that
+ handled each file in the data.
+
+ Lines, arcs, and file tracer names are stored for each source file. File
+ names in this API are case-sensitive, even on platforms with
+ case-insensitive file systems.
+
+ A data file either stores lines, or arcs, but not both.
+
+ A data file is associated with the data when the :class:`CoverageData`
+ is created, using the parameters `basename`, `suffix`, and `no_disk`. The
+ base name can be queried with :meth:`base_filename`, and the actual file
+ name being used is available from :meth:`data_filename`.
+
+ To read an existing coverage.py data file, use :meth:`read`. You can then
+ access the line, arc, or file tracer data with :meth:`lines`, :meth:`arcs`,
+ or :meth:`file_tracer`.
+
+ The :meth:`has_arcs` method indicates whether arc data is available. You
+ can get a set of the files in the data with :meth:`measured_files`. As
+ with most Python containers, you can determine if there is any data at all
+ by using this object as a boolean value.
+
+ The contexts for each line in a file can be read with
+ :meth:`contexts_by_lineno`.
+
+ To limit querying to certain contexts, use :meth:`set_query_context` or
+ :meth:`set_query_contexts`. These will narrow the focus of subsequent
+ :meth:`lines`, :meth:`arcs`, and :meth:`contexts_by_lineno` calls. The set
+ of all measured context names can be retrieved with
+ :meth:`measured_contexts`.
+
+ Most data files will be created by coverage.py itself, but you can use
+ methods here to create data files if you like. The :meth:`add_lines`,
+ :meth:`add_arcs`, and :meth:`add_file_tracers` methods add data, in ways
+ that are convenient for coverage.py.
+
+ To record data for contexts, use :meth:`set_context` to set a context to
+ be used for subsequent :meth:`add_lines` and :meth:`add_arcs` calls.
+
+ To add a source file without any measured data, use :meth:`touch_file`.
+
+ Write the data to its file with :meth:`write`.
+
+ You can clear the data in memory with :meth:`erase`. Two data collections
+ can be combined by using :meth:`update` on one :class:`CoverageData`,
+ passing it the other.
+
+ Data in a :class:`CoverageData` can be serialized and deserialized with
+ :meth:`dumps` and :meth:`loads`.
+
+ """
+
+ def __init__(self, basename=None, suffix=None, no_disk=False, warn=None, debug=None):
+ """Create a :class:`CoverageData` object to hold coverage-measured data.
+
+ Arguments:
+ basename (str): the base name of the data file, defaulting to
+ ".coverage".
+ suffix (str or bool): has the same meaning as the `data_suffix`
+ argument to :class:`coverage.Coverage`.
+ no_disk (bool): if True, keep all data in memory, and don't
+ write any disk file.
+ warn: a warning callback function, accepting a warning message
+ argument.
+ debug: a `DebugControl` object (optional)
+
+ """
+ self._no_disk = no_disk
+ self._basename = os.path.abspath(basename or ".coverage")
+ self._suffix = suffix
+ self._warn = warn
+ self._debug = debug or NoDebugging()
+
+ self._choose_filename()
+ self._file_map = {}
+ # Maps thread ids to SqliteDb objects.
+ self._dbs = {}
+ self._pid = os.getpid()
+
+ # Are we in sync with the data file?
+ self._have_used = False
+
+ self._has_lines = False
+ self._has_arcs = False
+
+ self._current_context = None
+ self._current_context_id = None
+ self._query_context_ids = None
+
+ def _choose_filename(self):
+ """Set self._filename based on inited attributes."""
+ if self._no_disk:
+ self._filename = ":memory:"
+ else:
+ self._filename = self._basename
+ suffix = filename_suffix(self._suffix)
+ if suffix:
+ self._filename += "." + suffix
+
+ def _reset(self):
+ """Reset our attributes."""
+ if self._dbs:
+ for db in self._dbs.values():
+ db.close()
+ self._dbs = {}
+ self._file_map = {}
+ self._have_used = False
+ self._current_context_id = None
+
+ def _create_db(self):
+ """Create a db file that doesn't exist yet.
+
+ Initializes the schema and certain metadata.
+ """
+ if self._debug.should('dataio'):
+ self._debug.write("Creating data file {!r}".format(self._filename))
+ self._dbs[get_thread_id()] = db = SqliteDb(self._filename, self._debug)
+ with db:
+ db.executescript(SCHEMA)
+ db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,))
+ db.executemany(
+ "insert into meta (key, value) values (?, ?)",
+ [
+ ('sys_argv', str(getattr(sys, 'argv', None))),
+ ('version', __version__),
+ ('when', datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
+ ]
+ )
+
+ def _open_db(self):
+ """Open an existing db file, and read its metadata."""
+ if self._debug.should('dataio'):
+ self._debug.write("Opening data file {!r}".format(self._filename))
+ self._dbs[get_thread_id()] = SqliteDb(self._filename, self._debug)
+ self._read_db()
+
+ def _read_db(self):
+ """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_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(
+ self._filename, exc
+ )
+ )
+ else:
+ if schema_version != SCHEMA_VERSION:
+ raise CoverageException(
+ "Couldn't use data file {!r}: wrong schema: {} instead of {}".format(
+ self._filename, schema_version, SCHEMA_VERSION
+ )
+ )
+
+ for row in db.execute("select value from meta where key = 'has_arcs'"):
+ self._has_arcs = bool(int(row[0]))
+ self._has_lines = not self._has_arcs
+
+ for path, file_id in db.execute("select path, id from file"):
+ self._file_map[path] = file_id
+
+ def _connect(self):
+ """Get the SqliteDb object to use."""
+ if get_thread_id() not in self._dbs:
+ if os.path.exists(self._filename):
+ self._open_db()
+ else:
+ self._create_db()
+ return self._dbs[get_thread_id()]
+
+ def __nonzero__(self):
+ if (get_thread_id() not in self._dbs and not os.path.exists(self._filename)):
+ return False
+ try:
+ with self._connect() as con:
+ rows = con.execute("select * from file limit 1")
+ return bool(list(rows))
+ except CoverageException:
+ return False
+
+ __bool__ = __nonzero__
+
+ @contract(returns='bytes')
+ def dumps(self):
+ """Serialize the current data to a byte string.
+
+ The format of the serialized data is not documented. It is only
+ suitable for use with :meth:`loads` in the same version of
+ coverage.py.
+
+ Returns:
+ A byte string of serialized data.
+
+ .. versionadded:: 5.0
+
+ """
+ if self._debug.should('dataio'):
+ self._debug.write("Dumping data from data file {!r}".format(self._filename))
+ with self._connect() as con:
+ return b'z' + zlib.compress(to_bytes(con.dump()))
+
+ @contract(data='bytes')
+ def loads(self, data):
+ """Deserialize data from :meth:`dumps`
+
+ Use with a newly-created empty :class:`CoverageData` object. It's
+ undefined what happens if the object already has data in it.
+
+ Arguments:
+ data: A byte string of serialized data produced by :meth:`dumps`.
+
+ .. versionadded:: 5.0
+
+ """
+ if self._debug.should('dataio'):
+ self._debug.write("Loading data into data file {!r}".format(self._filename))
+ if data[:1] != b'z':
+ raise CoverageException(
+ "Unrecognized serialization: {!r} (head of {} bytes)".format(data[:40], len(data))
+ )
+ script = to_string(zlib.decompress(data[1:]))
+ self._dbs[get_thread_id()] = db = SqliteDb(self._filename, self._debug)
+ with db:
+ db.executescript(script)
+ self._read_db()
+ self._have_used = True
+
+ def _file_id(self, filename, add=False):
+ """Get the file id for `filename`.
+
+ If filename is not in the database yet, add it if `add` is True.
+ If `add` is not True, return None.
+ """
+ if filename not in self._file_map:
+ if add:
+ with self._connect() as con:
+ cur = con.execute("insert or replace into file (path) values (?)", (filename,))
+ self._file_map[filename] = cur.lastrowid
+ return self._file_map.get(filename)
+
+ def _context_id(self, context):
+ """Get the id for a context."""
+ assert context is not None
+ self._start_using()
+ with self._connect() as con:
+ row = con.execute_one("select id from context where context = ?", (context,))
+ if row is not None:
+ return row[0]
+ else:
+ return None
+
+ def set_context(self, context):
+ """Set the current context for future :meth:`add_lines` etc.
+
+ `context` is a str, the name of the context to use for the next data
+ additions. The context persists until the next :meth:`set_context`.
+
+ .. versionadded:: 5.0
+
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Setting context: %r" % (context,))
+ self._current_context = context
+ self._current_context_id = None
+
+ def _set_context_id(self):
+ """Use the _current_context to set _current_context_id."""
+ context = self._current_context or ""
+ context_id = self._context_id(context)
+ if context_id is not None:
+ self._current_context_id = context_id
+ else:
+ with self._connect() as con:
+ cur = con.execute("insert into context (context) values (?)", (context,))
+ self._current_context_id = cur.lastrowid
+
+ def base_filename(self):
+ """The base filename for storing data.
+
+ .. versionadded:: 5.0
+
+ """
+ return self._basename
+
+ def data_filename(self):
+ """Where is the data stored?
+
+ .. versionadded:: 5.0
+
+ """
+ return self._filename
+
+ def add_lines(self, line_data):
+ """Add measured line data.
+
+ `line_data` is a dictionary mapping file names to dictionaries::
+
+ { filename: { lineno: None, ... }, ...}
+
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Adding lines: %d files, %d lines total" % (
+ len(line_data), sum(len(lines) for lines in line_data.values())
+ ))
+ self._start_using()
+ self._choose_lines_or_arcs(lines=True)
+ if not line_data:
+ return
+ with self._connect() as con:
+ self._set_context_id()
+ for filename, linenos in iitems(line_data):
+ linemap = nums_to_numbits(linenos)
+ file_id = self._file_id(filename, add=True)
+ query = "select numbits from line_bits where file_id = ? and context_id = ?"
+ existing = list(con.execute(query, (file_id, self._current_context_id)))
+ if existing:
+ linemap = numbits_union(linemap, existing[0][0])
+
+ con.execute(
+ "insert or replace into line_bits "
+ " (file_id, context_id, numbits) values (?, ?, ?)",
+ (file_id, self._current_context_id, linemap),
+ )
+
+ def add_arcs(self, arc_data):
+ """Add measured arc data.
+
+ `arc_data` is a dictionary mapping file names to dictionaries::
+
+ { filename: { (l1,l2): None, ... }, ...}
+
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Adding arcs: %d files, %d arcs total" % (
+ len(arc_data), sum(len(arcs) for arcs in arc_data.values())
+ ))
+ self._start_using()
+ self._choose_lines_or_arcs(arcs=True)
+ if not arc_data:
+ return
+ with self._connect() as con:
+ self._set_context_id()
+ for filename, arcs in iitems(arc_data):
+ file_id = self._file_id(filename, add=True)
+ data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs]
+ con.executemany(
+ "insert or ignore into arc "
+ "(file_id, context_id, fromno, tono) values (?, ?, ?, ?)",
+ data,
+ )
+
+ def _choose_lines_or_arcs(self, lines=False, arcs=False):
+ """Force the data file to choose between lines and arcs."""
+ 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")
+ if arcs and self._has_lines:
+ raise CoverageException("Can't add arcs to existing line data")
+ if not self._has_arcs and not self._has_lines:
+ self._has_lines = lines
+ self._has_arcs = arcs
+ with self._connect() as con:
+ con.execute(
+ "insert into meta (key, value) values (?, ?)",
+ ('has_arcs', str(int(arcs)))
+ )
+
+ def add_file_tracers(self, file_tracers):
+ """Add per-file plugin information.
+
+ `file_tracers` is { filename: plugin_name, ... }
+
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Adding file tracers: %d files" % (len(file_tracers),))
+ if not file_tracers:
+ return
+ self._start_using()
+ with self._connect() as con:
+ for filename, plugin_name in iitems(file_tracers):
+ file_id = self._file_id(filename)
+ if file_id is None:
+ raise CoverageException(
+ "Can't add file tracer data for unmeasured file '%s'" % (filename,)
+ )
+
+ existing_plugin = self.file_tracer(filename)
+ if existing_plugin:
+ if existing_plugin != plugin_name:
+ raise CoverageException(
+ "Conflicting file tracer name for '%s': %r vs %r" % (
+ filename, existing_plugin, plugin_name,
+ )
+ )
+ elif plugin_name:
+ con.execute(
+ "insert into tracer (file_id, tracer) values (?, ?)",
+ (file_id, plugin_name)
+ )
+
+ def touch_file(self, filename, plugin_name=""):
+ """Ensure that `filename` appears in the data, empty if needed.
+
+ `plugin_name` is the name of the plugin responsible for this file. It is used
+ to associate the right filereporter, etc.
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Touching %r" % (filename,))
+ self._start_using()
+ if not self._has_arcs and not self._has_lines:
+ raise CoverageException("Can't touch files in an empty CoverageData")
+
+ self._file_id(filename, add=True)
+ if plugin_name:
+ # Set the tracer for this file
+ self.add_file_tracers({filename: plugin_name})
+
+ def update(self, other_data, aliases=None):
+ """Update this data with data from several other :class:`CoverageData` instances.
+
+ If `aliases` is provided, it's a `PathAliases` object that is used to
+ re-map paths to match the local machine's.
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Updating with data from %r" % (
+ getattr(other_data, '_filename', '???'),
+ ))
+ if self._has_lines and other_data._has_arcs:
+ raise CoverageException("Can't combine arc data with line data")
+ if self._has_arcs and other_data._has_lines:
+ raise CoverageException("Can't combine line data with arc data")
+
+ aliases = aliases or PathAliases()
+
+ # Force the database we're writing to to exist before we start nesting
+ # contexts.
+ self._start_using()
+
+ # Collector for all arcs, lines and tracers
+ other_data.read()
+ with other_data._connect() as conn:
+ # Get files data.
+ cur = conn.execute('select path from file')
+ files = {path: aliases.map(path) for (path,) in cur}
+ cur.close()
+
+ # Get contexts data.
+ cur = conn.execute('select context from context')
+ contexts = [context for (context,) in cur]
+ cur.close()
+
+ # Get arc data.
+ cur = conn.execute(
+ 'select file.path, context.context, arc.fromno, arc.tono '
+ 'from arc '
+ 'inner join file on file.id = arc.file_id '
+ 'inner join context on context.id = arc.context_id'
+ )
+ arcs = [(files[path], context, fromno, tono) for (path, context, fromno, tono) in cur]
+ cur.close()
+
+ # Get line data.
+ cur = conn.execute(
+ 'select file.path, context.context, line_bits.numbits '
+ 'from line_bits '
+ 'inner join file on file.id = line_bits.file_id '
+ 'inner join context on context.id = line_bits.context_id'
+ )
+ lines = {
+ (files[path], context): numbits
+ for (path, context, numbits) in cur
+ }
+ cur.close()
+
+ # Get tracer data.
+ cur = conn.execute(
+ 'select file.path, tracer '
+ 'from tracer '
+ 'inner join file on file.id = tracer.file_id'
+ )
+ tracers = {files[path]: tracer for (path, tracer) in cur}
+ cur.close()
+
+ with self._connect() as conn:
+ conn.con.isolation_level = 'IMMEDIATE'
+
+ # Get all tracers in the DB. Files not in the tracers are assumed
+ # to have an empty string tracer. Since Sqlite does not support
+ # full outer joins, we have to make two queries to fill the
+ # dictionary.
+ this_tracers = {path: '' for path, in conn.execute('select path from file')}
+ this_tracers.update({
+ aliases.map(path): tracer
+ for path, tracer in conn.execute(
+ 'select file.path, tracer from tracer '
+ 'inner join file on file.id = tracer.file_id'
+ )
+ })
+
+ # Create all file and context rows in the DB.
+ conn.executemany(
+ 'insert or ignore into file (path) values (?)',
+ ((file,) for file in files.values())
+ )
+ file_ids = {
+ path: id
+ for id, path in conn.execute('select id, path from file')
+ }
+ conn.executemany(
+ 'insert or ignore into context (context) values (?)',
+ ((context,) for context in contexts)
+ )
+ context_ids = {
+ context: id
+ for id, context in conn.execute('select id, context from context')
+ }
+
+ # Prepare tracers and fail, if a conflict is found.
+ # tracer_paths is used to ensure consistency over the tracer data
+ # and tracer_map tracks the tracers to be inserted.
+ tracer_map = {}
+ for path in files.values():
+ this_tracer = this_tracers.get(path)
+ other_tracer = tracers.get(path, '')
+ # If there is no tracer, there is always the None tracer.
+ if this_tracer is not None and this_tracer != other_tracer:
+ raise CoverageException(
+ "Conflicting file tracer name for '%s': %r vs %r" % (
+ path, this_tracer, other_tracer
+ )
+ )
+ tracer_map[path] = other_tracer
+
+ # Prepare arc and line rows to be inserted by converting the file
+ # and context strings with integer ids. Then use the efficient
+ # `executemany()` to insert all rows at once.
+ arc_rows = (
+ (file_ids[file], context_ids[context], fromno, tono)
+ for file, context, fromno, tono in arcs
+ )
+
+ # Get line data.
+ cur = conn.execute(
+ 'select file.path, context.context, line_bits.numbits '
+ 'from line_bits '
+ 'inner join file on file.id = line_bits.file_id '
+ 'inner join context on context.id = line_bits.context_id'
+ )
+ for path, context, numbits in cur:
+ key = (aliases.map(path), context)
+ if key in lines:
+ numbits = numbits_union(lines[key], numbits)
+ lines[key] = numbits
+ cur.close()
+
+ if arcs:
+ self._choose_lines_or_arcs(arcs=True)
+
+ # Write the combined data.
+ conn.executemany(
+ 'insert or ignore into arc '
+ '(file_id, context_id, fromno, tono) values (?, ?, ?, ?)',
+ arc_rows
+ )
+
+ if lines:
+ self._choose_lines_or_arcs(lines=True)
+ conn.execute("delete from line_bits")
+ conn.executemany(
+ "insert into line_bits "
+ "(file_id, context_id, numbits) values (?, ?, ?)",
+ [
+ (file_ids[file], context_ids[context], numbits)
+ for (file, context), numbits in lines.items()
+ ]
+ )
+ conn.executemany(
+ 'insert or ignore into tracer (file_id, tracer) values (?, ?)',
+ ((file_ids[filename], tracer) for filename, tracer in tracer_map.items())
+ )
+
+ # Update all internal cache data.
+ self._reset()
+ self.read()
+
+ def erase(self, parallel=False):
+ """Erase the data in this object.
+
+ If `parallel` is true, then also deletes data files created from the
+ basename by parallel-mode.
+
+ """
+ self._reset()
+ if self._no_disk:
+ return
+ if self._debug.should('dataio'):
+ self._debug.write("Erasing data file {!r}".format(self._filename))
+ file_be_gone(self._filename)
+ if parallel:
+ data_dir, local = os.path.split(self._filename)
+ localdot = local + '.*'
+ pattern = os.path.join(os.path.abspath(data_dir), localdot)
+ for filename in glob.glob(pattern):
+ if self._debug.should('dataio'):
+ self._debug.write("Erasing parallel data file {!r}".format(filename))
+ file_be_gone(filename)
+
+ def read(self):
+ """Start using an existing data file."""
+ with self._connect(): # TODO: doesn't look right
+ self._have_used = True
+
+ def write(self):
+ """Ensure the data is written to the data file."""
+ pass
+
+ def _start_using(self):
+ """Call this before using the database at all."""
+ if self._pid != os.getpid():
+ # Looks like we forked! Have to start a new data file.
+ self._reset()
+ self._choose_filename()
+ self._pid = os.getpid()
+ if not self._have_used:
+ self.erase()
+ self._have_used = True
+
+ def has_arcs(self):
+ """Does the database have arcs (True) or lines (False)."""
+ return bool(self._has_arcs)
+
+ def measured_files(self):
+ """A set of all files that had been measured."""
+ return set(self._file_map)
+
+ def measured_contexts(self):
+ """A set of all contexts that have been measured.
+
+ .. versionadded:: 5.0
+
+ """
+ self._start_using()
+ with self._connect() as con:
+ contexts = set(row[0] for row in con.execute("select distinct(context) from context"))
+ return contexts
+
+ def file_tracer(self, filename):
+ """Get the plugin name of the file tracer for a file.
+
+ Returns the name of the plugin that handles this file. If the file was
+ measured, but didn't use a plugin, then "" is returned. If the file
+ was not measured, then None is returned.
+
+ """
+ self._start_using()
+ with self._connect() as con:
+ file_id = self._file_id(filename)
+ if file_id is None:
+ return None
+ 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.
+
+ def set_query_context(self, context):
+ """Set a context for subsequent querying.
+
+ The next :meth:`lines`, :meth:`arcs`, or :meth:`contexts_by_lineno`
+ calls will be limited to only one context. `context` is a string which
+ must match a context exactly. If it does not, no exception is raised,
+ but queries will return no data.
+
+ .. versionadded:: 5.0
+
+ """
+ self._start_using()
+ with self._connect() as con:
+ cur = con.execute("select id from context where context = ?", (context,))
+ self._query_context_ids = [row[0] for row in cur.fetchall()]
+
+ def set_query_contexts(self, contexts):
+ """Set a number of contexts for subsequent querying.
+
+ The next :meth:`lines`, :meth:`arcs`, or :meth:`contexts_by_lineno`
+ calls will be limited to the specified contexts. `contexts` is a list
+ of Python regular expressions. Contexts will be matched using
+ :func:`re.search <python:re.search>`. Data will be included in query
+ results if they are part of any of the contexts matched.
+
+ .. versionadded:: 5.0
+
+ """
+ self._start_using()
+ if contexts:
+ with self._connect() as con:
+ context_clause = ' or '.join(['context regexp ?'] * len(contexts))
+ cur = con.execute("select id from context where " + context_clause, contexts)
+ self._query_context_ids = [row[0] for row in cur.fetchall()]
+ else:
+ self._query_context_ids = None
+
+ def lines(self, filename):
+ """Get the list of lines executed for a file.
+
+ If the file was not measured, returns None. A file might be measured,
+ and have no lines executed, in which case an empty list is returned.
+
+ If the file was executed, returns a list of integers, the line numbers
+ executed in the file. The list is in no particular order.
+
+ """
+ self._start_using()
+ if self.has_arcs():
+ 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))
+
+ with self._connect() as con:
+ file_id = self._file_id(filename)
+ if file_id is None:
+ return None
+ else:
+ query = "select numbits from line_bits where file_id = ?"
+ data = [file_id]
+ if self._query_context_ids is not None:
+ ids_array = ', '.join('?' * len(self._query_context_ids))
+ query += " and context_id in (" + ids_array + ")"
+ data += self._query_context_ids
+ bitmaps = list(con.execute(query, data))
+ nums = set()
+ for row in bitmaps:
+ nums.update(numbits_to_nums(row[0]))
+ return list(nums)
+
+ def arcs(self, filename):
+ """Get the list of arcs executed for a file.
+
+ If the file was not measured, returns None. A file might be measured,
+ and have no arcs executed, in which case an empty list is returned.
+
+ If the file was executed, returns a list of 2-tuples of integers. Each
+ pair is a starting line number and an ending line number for a
+ transition from one line to another. The list is in no particular
+ order.
+
+ Negative numbers have special meaning. If the starting line number is
+ -N, it represents an entry to the code object that starts at line N.
+ If the ending ling number is -N, it's an exit from the code object that
+ starts at line N.
+
+ """
+ self._start_using()
+ with self._connect() as con:
+ file_id = self._file_id(filename)
+ if file_id is None:
+ return None
+ else:
+ query = "select distinct fromno, tono from arc where file_id = ?"
+ data = [file_id]
+ if self._query_context_ids is not None:
+ ids_array = ', '.join('?' * len(self._query_context_ids))
+ query += " and context_id in (" + ids_array + ")"
+ data += self._query_context_ids
+ arcs = con.execute(query, data)
+ return list(arcs)
+
+ def contexts_by_lineno(self, filename):
+ """Get the contexts for each line in a file.
+
+ Returns:
+ A dict mapping line numbers to a list of context names.
+
+ .. versionadded:: 5.0
+
+ """
+ lineno_contexts_map = collections.defaultdict(list)
+ self._start_using()
+ with self._connect() as con:
+ file_id = self._file_id(filename)
+ if file_id is None:
+ return lineno_contexts_map
+ if self.has_arcs():
+ query = (
+ "select arc.fromno, arc.tono, context.context "
+ "from arc, context "
+ "where arc.file_id = ? and arc.context_id = context.id"
+ )
+ data = [file_id]
+ if self._query_context_ids is not None:
+ ids_array = ', '.join('?' * len(self._query_context_ids))
+ query += " and arc.context_id in (" + ids_array + ")"
+ data += self._query_context_ids
+ for fromno, tono, context in con.execute(query, data):
+ if context not in lineno_contexts_map[fromno]:
+ lineno_contexts_map[fromno].append(context)
+ if context not in lineno_contexts_map[tono]:
+ lineno_contexts_map[tono].append(context)
+ else:
+ query = (
+ "select l.numbits, c.context from line_bits l, context c "
+ "where l.context_id = c.id "
+ "and file_id = ?"
+ )
+ data = [file_id]
+ if self._query_context_ids is not None:
+ ids_array = ', '.join('?' * len(self._query_context_ids))
+ query += " and l.context_id in (" + ids_array + ")"
+ data += self._query_context_ids
+ for numbits, context in con.execute(query, data):
+ for lineno in numbits_to_nums(numbits):
+ lineno_contexts_map[lineno].append(context)
+ return lineno_contexts_map
+
+ @classmethod
+ def sys_info(cls):
+ """Our information for `Coverage.sys_info`.
+
+ Returns a list of (key, value) pairs.
+
+ """
+ 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),
+ ]
+
+
+class SqliteDb(SimpleReprMixin):
+ """A simple abstraction over a SQLite database.
+
+ Use as a context manager, then you can use it like a
+ :class:`python:sqlite3.Connection` object::
+
+ with SqliteDb(filename, debug_control) as db:
+ db.execute("insert into schema (version) values (?)", (SCHEMA_VERSION,))
+
+ """
+ def __init__(self, filename, debug):
+ self.debug = debug if debug.should('sql') else None
+ self.filename = filename
+ self.nest = 0
+ self.con = None
+
+ def _connect(self):
+ """Connect to the db and do universal initialization."""
+ if self.con is not None:
+ return
+
+ # SQLite on Windows on py2 won't open a file if the filename argument
+ # has non-ascii characters in it. Opening a relative file name avoids
+ # a problem if the current directory has non-ascii.
+ filename = self.filename
+ if env.WINDOWS and env.PY2:
+ try:
+ filename = os.path.relpath(self.filename)
+ except ValueError:
+ # ValueError can be raised under Windows when os.getcwd() returns a
+ # folder from a different drive than the drive of self.filename in
+ # which case we keep the original value of self.filename unchanged,
+ # hoping that we won't face the non-ascii directory problem.
+ pass
+
+ # It can happen that Python switches threads while the tracer writes
+ # data. The second thread will also try to write to the data,
+ # effectively causing a nested context. However, given the idempotent
+ # nature of the tracer operations, sharing a connection among threads
+ # is not a problem.
+ if self.debug:
+ self.debug.write("Connecting to {!r}".format(self.filename))
+ self.con = sqlite3.connect(filename, check_same_thread=False)
+ self.con.create_function('REGEXP', 2, _regexp)
+
+ # This pragma makes writing faster. It disables rollbacks, but we never need them.
+ # PyPy needs the .close() calls here, or sqlite gets twisted up:
+ # https://bitbucket.org/pypy/pypy/issues/2872/default-isolation-mode-is-different-on
+ self.execute("pragma journal_mode=off").close()
+ # This pragma makes writing faster.
+ self.execute("pragma synchronous=off").close()
+
+ def close(self):
+ """If needed, close the connection."""
+ if self.con is not None and self.filename != ":memory:":
+ self.con.close()
+ self.con = None
+
+ def __enter__(self):
+ if self.nest == 0:
+ self._connect()
+ self.con.__enter__()
+ self.nest += 1
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.nest -= 1
+ if self.nest == 0:
+ try:
+ self.con.__exit__(exc_type, exc_value, traceback)
+ self.close()
+ except Exception as exc:
+ if self.debug:
+ self.debug.write("EXCEPTION from __exit__: {}".format(exc))
+ raise
+
+ def execute(self, sql, parameters=()):
+ """Same as :meth:`python:sqlite3.Connection.execute`."""
+ if self.debug:
+ tail = " with {!r}".format(parameters) if parameters else ""
+ self.debug.write("Executing {!r}{}".format(sql, tail))
+ try:
+ return self.con.execute(sql, parameters)
+ except sqlite3.Error as exc:
+ msg = str(exc)
+ try:
+ # `execute` is the first thing we do with the database, so try
+ # hard to provide useful hints if something goes wrong now.
+ with open(self.filename, "rb") as bad_file:
+ cov4_sig = b"!coverage.py: This is a private format"
+ if bad_file.read(len(cov4_sig)) == cov4_sig:
+ msg = (
+ "Looks like a coverage 4.x data file. "
+ "Are you mixing versions of coverage?"
+ )
+ except Exception:
+ pass
+ if self.debug:
+ 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:
+ data = list(data)
+ self.debug.write("Executing many {!r} with {} rows".format(sql, len(data)))
+ return self.con.executemany(sql, data)
+
+ def executescript(self, script):
+ """Same as :meth:`python:sqlite3.Connection.executescript`."""
+ if self.debug:
+ self.debug.write("Executing script with {} chars: {}".format(
+ len(script), clipped_repr(script, 100),
+ ))
+ self.con.executescript(script)
+
+ def dump(self):
+ """Return a multi-line string, the SQL dump of the database."""
+ return "\n".join(self.con.iterdump())
+
+
+def _regexp(text, pattern):
+ """A regexp function for SQLite."""
+ return re.search(text, pattern) is not None
diff --git a/third_party/python/coverage/coverage/summary.py b/third_party/python/coverage/coverage/summary.py
new file mode 100644
index 0000000000..97d9fff075
--- /dev/null
+++ b/third_party/python/coverage/coverage/summary.py
@@ -0,0 +1,155 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Summary reporting"""
+
+import sys
+
+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
+
+
+class SummaryReporter(object):
+ """A reporter for writing the summary report."""
+
+ def __init__(self, coverage):
+ self.coverage = coverage
+ self.config = self.coverage.config
+ self.branches = coverage.get_data().has_arcs()
+ self.outfile = None
+ self.fr_analysis = []
+ self.skipped_count = 0
+ self.empty_count = 0
+ self.total = Numbers()
+ self.fmt_err = u"%s %s: %s"
+
+ def writeout(self, line):
+ """Write a line to the output, adding a newline."""
+ if env.PY2:
+ line = line.encode(output_encoding())
+ self.outfile.write(line.rstrip())
+ self.outfile.write("\n")
+
+ def report(self, morfs, outfile=None):
+ """Writes a report summarizing coverage statistics per module.
+
+ `outfile` is a file object to write the summary to. It must be opened
+ for native strings (bytes on Python 2, Unicode on Python 3).
+
+ """
+ self.outfile = outfile or sys.stdout
+
+ self.coverage.get_data().set_query_contexts(self.config.report_contexts)
+ for fr, analysis in get_analysis_to_report(self.coverage, morfs):
+ self.report_one_file(fr, analysis)
+
+ # Prepare the formatting strings, header, and column sorting.
+ max_name = max([len(fr.relative_filename()) for (fr, analysis) in self.fr_analysis] + [5])
+ fmt_name = u"%%- %ds " % max_name
+ fmt_skip_covered = u"\n%s file%s skipped due to complete coverage."
+ fmt_skip_empty = u"\n%s empty file%s skipped."
+
+ header = (fmt_name % "Name") + u" Stmts Miss"
+ fmt_coverage = fmt_name + u"%6d %6d"
+ if self.branches:
+ header += u" Branch BrPart"
+ fmt_coverage += u" %6d %6d"
+ width100 = Numbers.pc_str_width()
+ header += u"%*s" % (width100+4, "Cover")
+ fmt_coverage += u"%%%ds%%%%" % (width100+3,)
+ if self.config.show_missing:
+ header += u" Missing"
+ fmt_coverage += u" %s"
+ rule = u"-" * len(header)
+
+ column_order = dict(name=0, stmts=1, miss=2, cover=-1)
+ if self.branches:
+ column_order.update(dict(branch=3, brpart=4))
+
+ # Write the header
+ self.writeout(header)
+ self.writeout(rule)
+
+ # `lines` is a list of pairs, (line text, line values). The line text
+ # is a string that will be printed, and line values is a tuple of
+ # sortable values.
+ lines = []
+
+ for (fr, analysis) in self.fr_analysis:
+ try:
+ 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))
+
+ # Sort the lines and write them out.
+ if getattr(self.config, 'sort', None):
+ position = column_order.get(self.config.sort.lower())
+ if position is None:
+ raise CoverageException("Invalid sorting option: {!r}".format(self.config.sort))
+ lines.sort(key=lambda l: (l[1][position], l[0]))
+
+ 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:
+ self.writeout(rule)
+ args = ("TOTAL", self.total.n_statements, self.total.n_missing)
+ if self.branches:
+ args += (self.total.n_branches, self.total.n_partial_branches)
+ args += (self.total.pc_covered_str,)
+ if self.config.show_missing:
+ args += ("",)
+ self.writeout(fmt_coverage % args)
+
+ # Write other final lines.
+ if not self.total.n_files and not self.skipped_count:
+ raise CoverageException("No data to report.")
+
+ if self.config.skip_covered and self.skipped_count:
+ self.writeout(
+ fmt_skip_covered % (self.skipped_count, 's' if self.skipped_count > 1 else '')
+ )
+ if self.config.skip_empty and self.empty_count:
+ self.writeout(
+ fmt_skip_empty % (self.empty_count, 's' if self.empty_count > 1 else '')
+ )
+
+ return self.total.n_statements and self.total.pc_covered
+
+ def report_one_file(self, fr, analysis):
+ """Report on just one file, the callback from report()."""
+ nums = analysis.numbers
+ self.total += nums
+
+ no_missing_lines = (nums.n_missing == 0)
+ no_missing_branches = (nums.n_partial_branches == 0)
+ if self.config.skip_covered and no_missing_lines and no_missing_branches:
+ # Don't report on 100% files.
+ self.skipped_count += 1
+ elif self.config.skip_empty and nums.n_statements == 0:
+ # Don't report on empty files.
+ self.empty_count += 1
+ else:
+ self.fr_analysis.append((fr, analysis))
diff --git a/third_party/python/coverage/coverage/templite.py b/third_party/python/coverage/coverage/templite.py
new file mode 100644
index 0000000000..7d4024e0af
--- /dev/null
+++ b/third_party/python/coverage/coverage/templite.py
@@ -0,0 +1,302 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""A simple Python template renderer, for a nano-subset of Django syntax.
+
+For a detailed discussion of this code, see this chapter from 500 Lines:
+http://aosabook.org/en/500L/a-template-engine.html
+
+"""
+
+# Coincidentally named the same as http://code.activestate.com/recipes/496702/
+
+import re
+
+from coverage import env
+
+
+class TempliteSyntaxError(ValueError):
+ """Raised when a template has a syntax error."""
+ pass
+
+
+class TempliteValueError(ValueError):
+ """Raised when an expression won't evaluate in a template."""
+ pass
+
+
+class CodeBuilder(object):
+ """Build source code conveniently."""
+
+ def __init__(self, indent=0):
+ self.code = []
+ self.indent_level = indent
+
+ def __str__(self):
+ return "".join(str(c) for c in self.code)
+
+ def add_line(self, line):
+ """Add a line of source to the code.
+
+ Indentation and newline will be added for you, don't provide them.
+
+ """
+ self.code.extend([" " * self.indent_level, line, "\n"])
+
+ def add_section(self):
+ """Add a section, a sub-CodeBuilder."""
+ section = CodeBuilder(self.indent_level)
+ self.code.append(section)
+ return section
+
+ INDENT_STEP = 4 # PEP8 says so!
+
+ def indent(self):
+ """Increase the current indent for following lines."""
+ self.indent_level += self.INDENT_STEP
+
+ def dedent(self):
+ """Decrease the current indent for following lines."""
+ self.indent_level -= self.INDENT_STEP
+
+ def get_globals(self):
+ """Execute the code, and return a dict of globals it defines."""
+ # A check that the caller really finished all the blocks they started.
+ assert self.indent_level == 0
+ # Get the Python source as a single string.
+ python_source = str(self)
+ # Execute the source, defining globals, and return them.
+ global_namespace = {}
+ exec(python_source, global_namespace)
+ return global_namespace
+
+
+class Templite(object):
+ """A simple template renderer, for a nano-subset of Django syntax.
+
+ Supported constructs are extended variable access::
+
+ {{var.modifier.modifier|filter|filter}}
+
+ loops::
+
+ {% for var in list %}...{% endfor %}
+
+ and ifs::
+
+ {% if var %}...{% endif %}
+
+ Comments are within curly-hash markers::
+
+ {# This will be ignored #}
+
+ Lines between `{% joined %}` and `{% endjoined %}` will have lines stripped
+ and joined. Be careful, this could join words together!
+
+ Any of these constructs can have a hyphen at the end (`-}}`, `-%}`, `-#}`),
+ which will collapse the whitespace following the tag.
+
+ Construct a Templite with the template text, then use `render` against a
+ dictionary context to create a finished string::
+
+ templite = Templite('''
+ <h1>Hello {{name|upper}}!</h1>
+ {% for topic in topics %}
+ <p>You are interested in {{topic}}.</p>
+ {% endif %}
+ ''',
+ {'upper': str.upper},
+ )
+ text = templite.render({
+ 'name': "Ned",
+ 'topics': ['Python', 'Geometry', 'Juggling'],
+ })
+
+ """
+ def __init__(self, text, *contexts):
+ """Construct a Templite with the given `text`.
+
+ `contexts` are dictionaries of values to use for future renderings.
+ These are good for filters and global values.
+
+ """
+ self.context = {}
+ for context in contexts:
+ self.context.update(context)
+
+ self.all_vars = set()
+ self.loop_vars = set()
+
+ # We construct a function in source form, then compile it and hold onto
+ # it, and execute it to render the template.
+ code = CodeBuilder()
+
+ code.add_line("def render_function(context, do_dots):")
+ code.indent()
+ vars_code = code.add_section()
+ code.add_line("result = []")
+ code.add_line("append_result = result.append")
+ code.add_line("extend_result = result.extend")
+ if env.PY2:
+ code.add_line("to_str = unicode")
+ else:
+ code.add_line("to_str = str")
+
+ buffered = []
+
+ def flush_output():
+ """Force `buffered` to the code builder."""
+ if len(buffered) == 1:
+ code.add_line("append_result(%s)" % buffered[0])
+ elif len(buffered) > 1:
+ code.add_line("extend_result([%s])" % ", ".join(buffered))
+ del buffered[:]
+
+ ops_stack = []
+
+ # Split the text to form a list of tokens.
+ tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
+
+ squash = in_joined = False
+
+ for token in tokens:
+ if token.startswith('{'):
+ start, end = 2, -2
+ squash = (token[-3] == '-')
+ if squash:
+ end = -3
+
+ if token.startswith('{#'):
+ # Comment: ignore it and move on.
+ continue
+ elif token.startswith('{{'):
+ # An expression to evaluate.
+ expr = self._expr_code(token[start:end].strip())
+ buffered.append("to_str(%s)" % expr)
+ else:
+ # token.startswith('{%')
+ # Action tag: split into words and parse further.
+ flush_output()
+
+ words = token[start:end].strip().split()
+ if words[0] == 'if':
+ # An if statement: evaluate the expression to determine if.
+ if len(words) != 2:
+ self._syntax_error("Don't understand if", token)
+ ops_stack.append('if')
+ code.add_line("if %s:" % self._expr_code(words[1]))
+ code.indent()
+ elif words[0] == 'for':
+ # A loop: iterate over expression result.
+ if len(words) != 4 or words[2] != 'in':
+ self._syntax_error("Don't understand for", token)
+ ops_stack.append('for')
+ self._variable(words[1], self.loop_vars)
+ code.add_line(
+ "for c_%s in %s:" % (
+ words[1],
+ self._expr_code(words[3])
+ )
+ )
+ code.indent()
+ elif words[0] == 'joined':
+ ops_stack.append('joined')
+ in_joined = True
+ elif words[0].startswith('end'):
+ # Endsomething. Pop the ops stack.
+ if len(words) != 1:
+ self._syntax_error("Don't understand end", token)
+ end_what = words[0][3:]
+ if not ops_stack:
+ self._syntax_error("Too many ends", token)
+ start_what = ops_stack.pop()
+ if start_what != end_what:
+ self._syntax_error("Mismatched end tag", end_what)
+ if end_what == 'joined':
+ in_joined = False
+ else:
+ code.dedent()
+ else:
+ self._syntax_error("Don't understand tag", words[0])
+ else:
+ # Literal content. If it isn't empty, output it.
+ if in_joined:
+ token = re.sub(r"\s*\n\s*", "", token.strip())
+ elif squash:
+ token = token.lstrip()
+ if token:
+ buffered.append(repr(token))
+
+ if ops_stack:
+ self._syntax_error("Unmatched action tag", ops_stack[-1])
+
+ flush_output()
+
+ for var_name in self.all_vars - self.loop_vars:
+ vars_code.add_line("c_%s = context[%r]" % (var_name, var_name))
+
+ code.add_line('return "".join(result)')
+ code.dedent()
+ self._render_function = code.get_globals()['render_function']
+
+ def _expr_code(self, expr):
+ """Generate a Python expression for `expr`."""
+ if "|" in expr:
+ pipes = expr.split("|")
+ code = self._expr_code(pipes[0])
+ for func in pipes[1:]:
+ self._variable(func, self.all_vars)
+ code = "c_%s(%s)" % (func, code)
+ elif "." in expr:
+ dots = expr.split(".")
+ code = self._expr_code(dots[0])
+ args = ", ".join(repr(d) for d in dots[1:])
+ code = "do_dots(%s, %s)" % (code, args)
+ else:
+ self._variable(expr, self.all_vars)
+ code = "c_%s" % expr
+ return code
+
+ def _syntax_error(self, msg, thing):
+ """Raise a syntax error using `msg`, and showing `thing`."""
+ raise TempliteSyntaxError("%s: %r" % (msg, thing))
+
+ def _variable(self, name, vars_set):
+ """Track that `name` is used as a variable.
+
+ Adds the name to `vars_set`, a set of variable names.
+
+ Raises an syntax error if `name` is not a valid name.
+
+ """
+ if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
+ self._syntax_error("Not a valid name", name)
+ vars_set.add(name)
+
+ def render(self, context=None):
+ """Render this template by applying it to `context`.
+
+ `context` is a dictionary of values to use in this rendering.
+
+ """
+ # Make the complete context we'll use.
+ render_context = dict(self.context)
+ if context:
+ render_context.update(context)
+ return self._render_function(render_context, self._do_dots)
+
+ def _do_dots(self, value, *dots):
+ """Evaluate dotted expressions at run-time."""
+ for dot in dots:
+ try:
+ value = getattr(value, dot)
+ except AttributeError:
+ try:
+ value = value[dot]
+ except (TypeError, KeyError):
+ raise TempliteValueError(
+ "Couldn't evaluate %r.%s" % (value, dot)
+ )
+ if callable(value):
+ value = value()
+ return value
diff --git a/third_party/python/coverage/coverage/tomlconfig.py b/third_party/python/coverage/coverage/tomlconfig.py
new file mode 100644
index 0000000000..25542f99ef
--- /dev/null
+++ b/third_party/python/coverage/coverage/tomlconfig.py
@@ -0,0 +1,164 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""TOML configuration support for coverage.py"""
+
+import io
+import os
+import re
+
+from coverage import env
+from coverage.backward import configparser, path_types
+from coverage.misc import CoverageException, substitute_variables
+
+
+class TomlDecodeError(Exception):
+ """An exception class that exists even when toml isn't installed."""
+ pass
+
+
+class TomlConfigParser:
+ """TOML file reading with the interface of HandyConfigParser."""
+
+ # This class has the same interface as config.HandyConfigParser, no
+ # need for docstrings.
+ # pylint: disable=missing-function-docstring
+
+ def __init__(self, our_file):
+ self.our_file = our_file
+ 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)
+ filename = filenames
+ if env.PYVERSION >= (3, 6):
+ filename = os.fspath(filename)
+
+ try:
+ with io.open(filename, encoding='utf-8') as fp:
+ toml_text = fp.read()
+ except IOError:
+ return []
+ if toml:
+ toml_text = substitute_variables(toml_text, os.environ)
+ try:
+ self.data = toml.loads(toml_text)
+ except toml.TomlDecodeError as err:
+ raise TomlDecodeError(*err.args)
+ return [filename]
+ else:
+ has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE)
+ if self.our_file or has_toml:
+ # Looks like they meant to read TOML, but we can't read it.
+ msg = "Can't read {!r} without TOML support. Install with [toml] extra"
+ raise CoverageException(msg.format(filename))
+ return []
+
+ def _get_section(self, section):
+ """Get a section from the data.
+
+ Arguments:
+ section (str): A section name, which can be dotted.
+
+ Returns:
+ name (str): the actual name of the section that was found, if any,
+ or None.
+ data (str): the dict of data in the section, or None if not found.
+
+ """
+ prefixes = ["tool.coverage."]
+ if self.our_file:
+ prefixes.append("")
+ for prefix in prefixes:
+ real_section = prefix + section
+ parts = real_section.split(".")
+ try:
+ data = self.data[parts[0]]
+ for part in parts[1:]:
+ data = data[part]
+ except KeyError:
+ continue
+ break
+ else:
+ return None, None
+ return real_section, data
+
+ def _get(self, section, option):
+ """Like .get, but returns the real section name and the value."""
+ name, data = self._get_section(section)
+ if data is None:
+ raise configparser.NoSectionError(section)
+ try:
+ return name, data[option]
+ except KeyError:
+ raise configparser.NoOptionError(option, name)
+
+ def has_option(self, section, option):
+ _, data = self._get_section(section)
+ if data is None:
+ return False
+ return option in data
+
+ def has_section(self, section):
+ name, _ = self._get_section(section)
+ return name
+
+ def options(self, section):
+ _, data = self._get_section(section)
+ if data is None:
+ raise configparser.NoSectionError(section)
+ return list(data.keys())
+
+ def get_section(self, section):
+ _, data = self._get_section(section)
+ return data
+
+ def get(self, section, option):
+ _, value = self._get(section, option)
+ return value
+
+ def _check_type(self, section, option, value, type_, type_desc):
+ if not isinstance(value, type_):
+ raise ValueError(
+ 'Option {!r} in section {!r} is not {}: {!r}'
+ .format(option, section, type_desc, value)
+ )
+
+ def getboolean(self, section, option):
+ name, value = self._get(section, option)
+ self._check_type(name, option, value, bool, "a boolean")
+ return value
+
+ def getlist(self, section, option):
+ name, values = self._get(section, option)
+ self._check_type(name, option, values, list, "a list")
+ return values
+
+ def getregexlist(self, section, option):
+ name, values = self._get(section, option)
+ self._check_type(name, option, values, list, "a list")
+ for value in values:
+ value = value.strip()
+ try:
+ re.compile(value)
+ except re.error as e:
+ raise CoverageException(
+ "Invalid [%s].%s value %r: %s" % (name, option, value, e)
+ )
+ return values
+
+ def getint(self, section, option):
+ name, value = self._get(section, option)
+ self._check_type(name, option, value, int, "an integer")
+ return value
+
+ def getfloat(self, section, option):
+ name, value = self._get(section, option)
+ if isinstance(value, int):
+ value = float(value)
+ self._check_type(name, option, value, float, "a float")
+ return value
diff --git a/third_party/python/coverage/coverage/version.py b/third_party/python/coverage/coverage/version.py
new file mode 100644
index 0000000000..8e72165d3b
--- /dev/null
+++ b/third_party/python/coverage/coverage/version.py
@@ -0,0 +1,33 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""The version and URL for coverage.py"""
+# This file is exec'ed in setup.py, don't import anything!
+
+# Same semantics as sys.version_info.
+version_info = (5, 1, 0, 'final', 0)
+
+
+def _make_version(major, minor, micro, releaselevel, serial):
+ """Create a readable version string from version_info tuple components."""
+ assert releaselevel in ['alpha', 'beta', 'candidate', 'final']
+ version = "%d.%d" % (major, minor)
+ if micro:
+ version += ".%d" % (micro,)
+ if releaselevel != 'final':
+ short = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc'}[releaselevel]
+ version += "%s%d" % (short, serial)
+ return version
+
+
+def _make_url(major, minor, micro, releaselevel, serial):
+ """Make the URL people should start at for this version of coverage.py."""
+ url = "https://coverage.readthedocs.io"
+ if releaselevel != 'final':
+ # For pre-releases, use a version-specific URL.
+ url += "/en/coverage-" + _make_version(major, minor, micro, releaselevel, serial)
+ return url
+
+
+__version__ = _make_version(*version_info)
+__url__ = _make_url(*version_info)
diff --git a/third_party/python/coverage/coverage/xmlreport.py b/third_party/python/coverage/coverage/xmlreport.py
new file mode 100644
index 0000000000..ad44775f2f
--- /dev/null
+++ b/third_party/python/coverage/coverage/xmlreport.py
@@ -0,0 +1,230 @@
+# coding: utf-8
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""XML reporting for coverage.py"""
+
+import os
+import os.path
+import sys
+import time
+import xml.dom.minidom
+
+from coverage import env
+from coverage import __url__, __version__, files
+from coverage.backward import iitems
+from coverage.misc import isolate_module
+from coverage.report import get_analysis_to_report
+
+os = isolate_module(os)
+
+
+DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd'
+
+
+def rate(hit, num):
+ """Return the fraction of `hit`/`num`, as a string."""
+ if num == 0:
+ return "1"
+ else:
+ return "%.4g" % (float(hit) / num)
+
+
+class XmlReporter(object):
+ """A reporter for writing Cobertura-style XML coverage results."""
+
+ def __init__(self, coverage):
+ self.coverage = coverage
+ self.config = self.coverage.config
+
+ self.source_paths = set()
+ if self.config.source:
+ for src in self.config.source:
+ if os.path.exists(src):
+ if not self.config.relative_files:
+ src = files.canonical_filename(src)
+ self.source_paths.add(src)
+ self.packages = {}
+ self.xml_out = None
+
+ def report(self, morfs, outfile=None):
+ """Generate a Cobertura-compatible XML report for `morfs`.
+
+ `morfs` is a list of modules or file names.
+
+ `outfile` is a file object to write the XML to.
+
+ """
+ # Initial setup.
+ outfile = outfile or sys.stdout
+ has_arcs = self.coverage.get_data().has_arcs()
+
+ # Create the DOM that will store the data.
+ impl = xml.dom.minidom.getDOMImplementation()
+ self.xml_out = impl.createDocument(None, "coverage", None)
+
+ # Write header stuff.
+ xcoverage = self.xml_out.documentElement
+ xcoverage.setAttribute("version", __version__)
+ xcoverage.setAttribute("timestamp", str(int(time.time()*1000)))
+ xcoverage.appendChild(self.xml_out.createComment(
+ " Generated by coverage.py: %s " % __url__
+ ))
+ xcoverage.appendChild(self.xml_out.createComment(" Based on %s " % DTD_URL))
+
+ # Call xml_file for each file in the data.
+ for fr, analysis in get_analysis_to_report(self.coverage, morfs):
+ self.xml_file(fr, analysis, has_arcs)
+
+ xsources = self.xml_out.createElement("sources")
+ xcoverage.appendChild(xsources)
+
+ # Populate the XML DOM with the source info.
+ for path in sorted(self.source_paths):
+ xsource = self.xml_out.createElement("source")
+ xsources.appendChild(xsource)
+ txt = self.xml_out.createTextNode(path)
+ xsource.appendChild(txt)
+
+ lnum_tot, lhits_tot = 0, 0
+ bnum_tot, bhits_tot = 0, 0
+
+ xpackages = self.xml_out.createElement("packages")
+ xcoverage.appendChild(xpackages)
+
+ # Populate the XML DOM with the package info.
+ for pkg_name, pkg_data in sorted(iitems(self.packages)):
+ class_elts, lhits, lnum, bhits, bnum = pkg_data
+ xpackage = self.xml_out.createElement("package")
+ xpackages.appendChild(xpackage)
+ xclasses = self.xml_out.createElement("classes")
+ xpackage.appendChild(xclasses)
+ for _, class_elt in sorted(iitems(class_elts)):
+ xclasses.appendChild(class_elt)
+ xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
+ xpackage.setAttribute("line-rate", rate(lhits, lnum))
+ if has_arcs:
+ branch_rate = rate(bhits, bnum)
+ else:
+ branch_rate = "0"
+ xpackage.setAttribute("branch-rate", branch_rate)
+ xpackage.setAttribute("complexity", "0")
+
+ lnum_tot += lnum
+ lhits_tot += lhits
+ bnum_tot += bnum
+ bhits_tot += bhits
+
+ xcoverage.setAttribute("lines-valid", str(lnum_tot))
+ xcoverage.setAttribute("lines-covered", str(lhits_tot))
+ xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot))
+ if has_arcs:
+ xcoverage.setAttribute("branches-valid", str(bnum_tot))
+ xcoverage.setAttribute("branches-covered", str(bhits_tot))
+ xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot))
+ else:
+ xcoverage.setAttribute("branches-covered", "0")
+ xcoverage.setAttribute("branches-valid", "0")
+ xcoverage.setAttribute("branch-rate", "0")
+ xcoverage.setAttribute("complexity", "0")
+
+ # Write the output file.
+ outfile.write(serialize_xml(self.xml_out))
+
+ # Return the total percentage.
+ denom = lnum_tot + bnum_tot
+ if denom == 0:
+ pct = 0.0
+ else:
+ pct = 100.0 * (lhits_tot + bhits_tot) / denom
+ return pct
+
+ def xml_file(self, fr, analysis, has_arcs):
+ """Add to the XML report for a single file."""
+
+ # 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("/", ".")
+
+ package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0])
+
+ xclass = self.xml_out.createElement("class")
+
+ xclass.appendChild(self.xml_out.createElement("methods"))
+
+ xlines = self.xml_out.createElement("lines")
+ xclass.appendChild(xlines)
+
+ xclass.setAttribute("name", os.path.relpath(rel_name, dirname))
+ xclass.setAttribute("filename", rel_name.replace("\\", "/"))
+ xclass.setAttribute("complexity", "0")
+
+ branch_stats = analysis.branch_stats()
+ missing_branch_arcs = analysis.missing_branch_arcs()
+
+ # For each statement, create an XML 'line' element.
+ for line in sorted(analysis.statements):
+ xline = self.xml_out.createElement("line")
+ xline.setAttribute("number", str(line))
+
+ # Q: can we get info about the number of times a statement is
+ # executed? If so, that should be recorded here.
+ xline.setAttribute("hits", str(int(line not in analysis.missing)))
+
+ if has_arcs:
+ if line in branch_stats:
+ total, taken = branch_stats[line]
+ xline.setAttribute("branch", "true")
+ xline.setAttribute(
+ "condition-coverage",
+ "%d%% (%d/%d)" % (100*taken//total, taken, total)
+ )
+ if line in missing_branch_arcs:
+ annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]]
+ xline.setAttribute("missing-branches", ",".join(annlines))
+ xlines.appendChild(xline)
+
+ class_lines = len(analysis.statements)
+ class_hits = class_lines - len(analysis.missing)
+
+ if has_arcs:
+ class_branches = sum(t for t, k in branch_stats.values())
+ missing_branches = sum(t - k for t, k in branch_stats.values())
+ class_br_hits = class_branches - missing_branches
+ else:
+ class_branches = 0.0
+ class_br_hits = 0.0
+
+ # Finalize the statistics that are collected in the XML DOM.
+ xclass.setAttribute("line-rate", rate(class_hits, class_lines))
+ if has_arcs:
+ branch_rate = rate(class_br_hits, class_branches)
+ else:
+ branch_rate = "0"
+ xclass.setAttribute("branch-rate", branch_rate)
+
+ package[0][rel_name] = xclass
+ package[1] += class_hits
+ package[2] += class_lines
+ package[3] += class_br_hits
+ package[4] += class_branches
+
+
+def serialize_xml(dom):
+ """Serialize a minidom node to XML."""
+ out = dom.toprettyxml()
+ if env.PY2:
+ out = out.encode("utf8")
+ return out
diff --git a/third_party/python/coverage/howto.txt b/third_party/python/coverage/howto.txt
new file mode 100644
index 0000000000..3653e830a7
--- /dev/null
+++ b/third_party/python/coverage/howto.txt
@@ -0,0 +1,122 @@
+* Release checklist
+
+- Check that the current virtualenv matches the current coverage branch.
+- Version number in coverage/version.py
+ version_info = (4, 0, 2, 'alpha', 1)
+ version_info = (4, 0, 2, 'beta', 1)
+ version_info = (4, 0, 2, 'candidate', 1)
+ version_info = (4, 0, 2, 'final', 0)
+- Python version number in classifiers in setup.py
+- Copyright date in NOTICE.txt
+- Update CHANGES.rst, including release date.
+ - don't forget the jump target
+- Update README.rst
+ - "New in x.y:"
+ - Python versions supported
+- Update docs
+ - Python versions in doc/index.rst
+ - Version of latest stable release in doc/index.rst
+ - Version, release, release_date and copyright date in doc/conf.py
+ - Look for CHANGEME comments
+ - Don't forget the man page: doc/python-coverage.1.txt
+ - Check that the docs build correctly:
+ $ tox -e doc
+ there will be warnings about the readthedocs links being broken,
+ because this version's docs haven't been published yet.
+ - Done with changes to source files, check them in.
+ - git push
+ - Generate new sample_html to get the latest, incl footer version number:
+ make clean
+ pip install -e .
+ cd ~/cog/trunk
+ rm -rf htmlcov
+ coverage run --branch --source=cogapp -m pytest -k CogTestsInMemory; coverage combine; coverage html
+ - IF PRE-RELEASE:
+ rm -f ~/coverage/trunk/doc/sample_html_beta/*.*
+ cp -r htmlcov/ ~/coverage/trunk/doc/sample_html_beta/
+ - IF NOT PRE-RELEASE:
+ rm -f ~/coverage/trunk/doc/sample_html/*.*
+ cp -r htmlcov/ ~/coverage/trunk/doc/sample_html/
+ cd ~/coverage/trunk
+ - IF NOT PRE-RELEASE:
+ check in the new sample html
+ - Build and publish docs:
+ - IF PRE-RELEASE:
+ $ make publishbeta
+ - ELSE:
+ $ make publish
+- Kits:
+ - Start fresh:
+ - $ make sterile
+ - Source kit and wheels:
+ - $ make kit wheel
+ - Linux wheels:
+ - $ make kit_linux
+ - Windows kits
+ - wait for over an hour for Appveyor to build kits.
+ - https://ci.appveyor.com/project/nedbat/coveragepy
+ - $ make download_appveyor
+ - examine the dist directory, and remove anything that looks malformed.
+ - test the pypi upload:
+ - $ make test_upload
+- Update PyPI:
+ - upload kits:
+ - $ make kit_upload
+- Tag the tree
+ - git tag coverage-3.0.1
+ - git push --tags
+- Bump version:
+ - coverage/version.py
+ - increment version number
+ - IF NOT PRE-RELEASE:
+ - set to alpha-0 if just released.
+ - CHANGES.rst
+ - add an "Unreleased" section to the top.
+ - git push
+- Update Tidelift:
+ - make upload_relnotes
+- Update readthedocs
+ - IF NOT PRE-RELEASE:
+ - update git "stable" branch to point to latest release
+ - git branch -f stable <latest-tag>
+ - git push --all
+ - visit https://readthedocs.org/projects/coverage/builds/
+ - wait for the new tag build to finish successfully.
+ - visit https://readthedocs.org/dashboard/coverage/advanced/
+ - change the default version to the new version
+ - visit https://readthedocs.org/projects/coverage/versions/
+ - find the latest tag in the inactive list, edit it, make it active.
+ - readthedocs won't find the tag until a commit is made on master.
+ - keep just the latest version of each x.y release, make the rest inactive.
+- Visit the fixed issues on GitHub and mention the version it was fixed in.
+ - make a milestone for the next release and move open issues into it.
+- Announce:
+ - twitter @coveragepy
+ - nedbatchelder.com blog post?
+ - testing-in-python mailing list?
+
+
+* Testing
+
+- Testing of Python code is handled by tox.
+ - Create and activate a virtualenv
+ - pip install -r requirements/dev.pip
+ - $ tox
+
+- Testing on Linux:
+ - $ make test_linux
+
+- For complete coverage testing:
+
+ $ make metacov
+
+ This will run coverage.py under its own measurement. You can do this in
+ different environments (Linux vs. Windows, for example), then copy the data
+ files (.metacov.*) to one machine for combination and reporting. To
+ combine and report:
+
+ $ make metahtml
+
+- To run the Javascript tests:
+
+ open tests/js/index.html in variety of browsers.
diff --git a/third_party/python/coverage/igor.py b/third_party/python/coverage/igor.py
new file mode 100644
index 0000000000..a742cb8e19
--- /dev/null
+++ b/third_party/python/coverage/igor.py
@@ -0,0 +1,395 @@
+# coding: utf-8
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Helper for building, testing, and linting coverage.py.
+
+To get portability, all these operations are written in Python here instead
+of in shell scripts, batch files, or Makefiles.
+
+"""
+
+import contextlib
+import fnmatch
+import glob
+import inspect
+import os
+import platform
+import sys
+import textwrap
+import warnings
+import zipfile
+
+import pytest
+
+
+@contextlib.contextmanager
+def ignore_warnings():
+ """Context manager to ignore warning within the with statement."""
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ yield
+
+
+# Functions named do_* are executable from the command line: do_blah is run
+# by "python igor.py blah".
+
+
+def do_show_env():
+ """Show the environment variables."""
+ print("Environment:")
+ for env in sorted(os.environ):
+ print(" %s = %r" % (env, os.environ[env]))
+
+
+def do_remove_extension():
+ """Remove the compiled C extension, no matter what its name."""
+
+ so_patterns = """
+ tracer.so
+ tracer.*.so
+ tracer.pyd
+ tracer.*.pyd
+ """.split()
+
+ for pattern in so_patterns:
+ pattern = os.path.join("coverage", pattern)
+ for filename in glob.glob(pattern):
+ try:
+ os.remove(filename)
+ except OSError:
+ pass
+
+
+def label_for_tracer(tracer):
+ """Get the label for these tests."""
+ if tracer == "py":
+ label = "with Python tracer"
+ else:
+ label = "with C tracer"
+
+ return label
+
+
+def should_skip(tracer):
+ """Is there a reason to skip these tests?"""
+ if tracer == "py":
+ # $set_env.py: COVERAGE_NO_PYTRACER - Don't run the tests under the Python tracer.
+ skipper = os.environ.get("COVERAGE_NO_PYTRACER")
+ else:
+ # $set_env.py: COVERAGE_NO_CTRACER - Don't run the tests under the C tracer.
+ skipper = os.environ.get("COVERAGE_NO_CTRACER")
+
+ if skipper:
+ msg = "Skipping tests " + label_for_tracer(tracer)
+ if len(skipper) > 1:
+ msg += ": " + skipper
+ else:
+ msg = ""
+
+ return msg
+
+
+def make_env_id(tracer):
+ """An environment id that will keep all the test runs distinct."""
+ impl = platform.python_implementation().lower()
+ version = "%s%s" % sys.version_info[:2]
+ if '__pypy__' in sys.builtin_module_names:
+ version += "_%s%s" % sys.pypy_version_info[:2]
+ env_id = "%s%s_%s" % (impl, version, tracer)
+ return env_id
+
+
+def run_tests(tracer, *runner_args):
+ """The actual running of tests."""
+ if 'COVERAGE_TESTING' not in os.environ:
+ os.environ['COVERAGE_TESTING'] = "True"
+ # $set_env.py: COVERAGE_ENV_ID - Use environment-specific test directories.
+ if 'COVERAGE_ENV_ID' in os.environ:
+ os.environ['COVERAGE_ENV_ID'] = make_env_id(tracer)
+ print_banner(label_for_tracer(tracer))
+ return pytest.main(list(runner_args))
+
+
+def run_tests_with_coverage(tracer, *runner_args):
+ """Run tests, but with coverage."""
+ # Need to define this early enough that the first import of env.py sees it.
+ os.environ['COVERAGE_TESTING'] = "True"
+ os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini')
+ os.environ['COVERAGE_HOME'] = os.getcwd()
+
+ # Create the .pth file that will let us measure coverage in sub-processes.
+ # The .pth file seems to have to be alphabetically after easy-install.pth
+ # or the sys.path entries aren't created right?
+ # There's an entry in "make clean" to get rid of this file.
+ pth_dir = os.path.dirname(pytest.__file__)
+ pth_path = os.path.join(pth_dir, "zzz_metacov.pth")
+ with open(pth_path, "w") as pth_file:
+ pth_file.write("import coverage; coverage.process_startup()\n")
+
+ suffix = "%s_%s" % (make_env_id(tracer), platform.platform())
+ os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov."+suffix)
+
+ import coverage
+ cov = coverage.Coverage(config_file="metacov.ini")
+ cov._warn_unimported_source = False
+ cov._warn_preimported_source = False
+ cov.start()
+
+ try:
+ # Re-import coverage to get it coverage tested! I don't understand all
+ # the mechanics here, but if I don't carry over the imported modules
+ # (in covmods), then things go haywire (os == None, eventually).
+ covmods = {}
+ covdir = os.path.split(coverage.__file__)[0]
+ # We have to make a list since we'll be deleting in the loop.
+ modules = list(sys.modules.items())
+ for name, mod in modules:
+ if name.startswith('coverage'):
+ if getattr(mod, '__file__', "??").startswith(covdir):
+ covmods[name] = mod
+ del sys.modules[name]
+ import coverage # pylint: disable=reimported
+ sys.modules.update(covmods)
+
+ # Run tests, with the arguments from our command line.
+ status = run_tests(tracer, *runner_args)
+
+ finally:
+ cov.stop()
+ os.remove(pth_path)
+
+ cov.combine()
+ cov.save()
+
+ return status
+
+
+def do_combine_html():
+ """Combine data from a meta-coverage run, and make the HTML and XML reports."""
+ import coverage
+ os.environ['COVERAGE_HOME'] = os.getcwd()
+ os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov")
+ cov = coverage.Coverage(config_file="metacov.ini")
+ cov.load()
+ cov.combine()
+ cov.save()
+ show_contexts = bool(os.environ.get('COVERAGE_CONTEXT'))
+ cov.html_report(show_contexts=show_contexts)
+ cov.xml_report()
+
+
+def do_test_with_tracer(tracer, *runner_args):
+ """Run tests with a particular tracer."""
+ # If we should skip these tests, skip them.
+ skip_msg = should_skip(tracer)
+ if skip_msg:
+ print(skip_msg)
+ return None
+
+ os.environ["COVERAGE_TEST_TRACER"] = tracer
+ if os.environ.get("COVERAGE_COVERAGE", "no") == "yes":
+ return run_tests_with_coverage(tracer, *runner_args)
+ else:
+ return run_tests(tracer, *runner_args)
+
+
+def do_zip_mods():
+ """Build the zipmods.zip file."""
+ zf = zipfile.ZipFile("tests/zipmods.zip", "w")
+
+ # Take one file from disk.
+ zf.write("tests/covmodzip1.py", "covmodzip1.py")
+
+ # The others will be various encodings.
+ source = textwrap.dedent(u"""\
+ # coding: {encoding}
+ text = u"{text}"
+ ords = {ords}
+ assert [ord(c) for c in text] == ords
+ print(u"All OK with {encoding}")
+ """)
+ # These encodings should match the list in tests/test_python.py
+ details = [
+ (u'utf8', u'ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ'),
+ (u'gb2312', u'你好,世界'),
+ (u'hebrew', u'שלום, עולם'),
+ (u'shift_jis', u'こんにちは世界'),
+ (u'cp1252', u'“hi”'),
+ ]
+ for encoding, text in details:
+ filename = 'encoded_{}.py'.format(encoding)
+ ords = [ord(c) for c in text]
+ source_text = source.format(encoding=encoding, text=text, ords=ords)
+ zf.writestr(filename, source_text.encode(encoding))
+
+ zf.close()
+
+ zf = zipfile.ZipFile("tests/covmain.zip", "w")
+ zf.write("coverage/__main__.py", "__main__.py")
+ zf.close()
+
+
+def do_install_egg():
+ """Install the egg1 egg for tests."""
+ # I am pretty certain there are easier ways to install eggs...
+ cur_dir = os.getcwd()
+ os.chdir("tests/eggsrc")
+ with ignore_warnings():
+ import distutils.core
+ distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"])
+ egg = glob.glob("dist/*.egg")[0]
+ distutils.core.run_setup(
+ "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg]
+ )
+ os.chdir(cur_dir)
+
+
+def do_check_eol():
+ """Check files for incorrect newlines and trailing whitespace."""
+
+ ignore_dirs = [
+ '.svn', '.hg', '.git',
+ '.tox*',
+ '*.egg-info',
+ '_build',
+ '_spell',
+ ]
+ checked = set()
+
+ def check_file(fname, crlf=True, trail_white=True):
+ """Check a single file for whitespace abuse."""
+ fname = os.path.relpath(fname)
+ if fname in checked:
+ return
+ checked.add(fname)
+
+ line = None
+ with open(fname, "rb") as f:
+ for n, line in enumerate(f, start=1):
+ if crlf:
+ if b"\r" in line:
+ print("%s@%d: CR found" % (fname, n))
+ return
+ if trail_white:
+ line = line[:-1]
+ if not crlf:
+ line = line.rstrip(b'\r')
+ if line.rstrip() != line:
+ print("%s@%d: trailing whitespace found" % (fname, n))
+ return
+
+ if line is not None and not line.strip():
+ print("%s: final blank line" % (fname,))
+
+ def check_files(root, patterns, **kwargs):
+ """Check a number of files for whitespace abuse."""
+ for where, dirs, files in os.walk(root):
+ for f in files:
+ fname = os.path.join(where, f)
+ for p in patterns:
+ if fnmatch.fnmatch(fname, p):
+ check_file(fname, **kwargs)
+ break
+ for ignore_dir in ignore_dirs:
+ ignored = []
+ for dir_name in dirs:
+ if fnmatch.fnmatch(dir_name, ignore_dir):
+ ignored.append(dir_name)
+ for dir_name in ignored:
+ dirs.remove(dir_name)
+
+ check_files("coverage", ["*.py"])
+ check_files("coverage/ctracer", ["*.c", "*.h"])
+ check_files("coverage/htmlfiles", ["*.html", "*.scss", "*.css", "*.js"])
+ check_files("tests", ["*.py"])
+ check_files("tests", ["*,cover"], trail_white=False)
+ check_files("tests/js", ["*.js", "*.html"])
+ check_file("setup.py")
+ check_file("igor.py")
+ check_file("Makefile")
+ check_file(".travis.yml")
+ check_files(".", ["*.rst", "*.txt"])
+ check_files(".", ["*.pip"])
+
+
+def print_banner(label):
+ """Print the version of Python."""
+ try:
+ impl = platform.python_implementation()
+ except AttributeError:
+ impl = "Python"
+
+ version = platform.python_version()
+
+ if '__pypy__' in sys.builtin_module_names:
+ version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info)
+
+ try:
+ which_python = os.path.relpath(sys.executable)
+ except ValueError:
+ # On Windows having a python executable on a different drive
+ # than the sources cannot be relative.
+ which_python = sys.executable
+ print('=== %s %s %s (%s) ===' % (impl, version, label, which_python))
+ sys.stdout.flush()
+
+
+def do_help():
+ """List the available commands"""
+ items = list(globals().items())
+ items.sort()
+ for name, value in items:
+ if name.startswith('do_'):
+ print("%-20s%s" % (name[3:], value.__doc__))
+
+
+def analyze_args(function):
+ """What kind of args does `function` expect?
+
+ Returns:
+ star, num_pos:
+ star(boolean): Does `function` accept *args?
+ num_args(int): How many positional arguments does `function` have?
+ """
+ try:
+ getargspec = inspect.getfullargspec
+ except AttributeError:
+ getargspec = inspect.getargspec
+ with ignore_warnings():
+ # DeprecationWarning: Use inspect.signature() instead of inspect.getfullargspec()
+ argspec = getargspec(function)
+ return bool(argspec[1]), len(argspec[0])
+
+
+def main(args):
+ """Main command-line execution for igor.
+
+ Verbs are taken from the command line, and extra words taken as directed
+ by the arguments needed by the handler.
+
+ """
+ while args:
+ verb = args.pop(0)
+ handler = globals().get('do_'+verb)
+ if handler is None:
+ print("*** No handler for %r" % verb)
+ return 1
+ star, num_args = analyze_args(handler)
+ if star:
+ # Handler has *args, give it all the rest of the command line.
+ handler_args = args
+ args = []
+ else:
+ # Handler has specific arguments, give it only what it needs.
+ handler_args = args[:num_args]
+ args = args[num_args:]
+ ret = handler(*handler_args)
+ # If a handler returns a failure-like value, stop.
+ if ret:
+ return ret
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/third_party/python/coverage/metacov.ini b/third_party/python/coverage/metacov.ini
new file mode 100644
index 0000000000..daabbf82f0
--- /dev/null
+++ b/third_party/python/coverage/metacov.ini
@@ -0,0 +1,88 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# Settings to use when using coverage.py to measure itself, known as
+# meta-coverage. This gets intricate because we need to keep the coverage
+# measurement happening in the tests separate from our own coverage measurement
+# of coverage.py itself.
+
+[run]
+branch = true
+data_file = ${COVERAGE_METAFILE?}
+parallel = true
+source =
+ ${COVERAGE_HOME-.}/coverage
+ ${COVERAGE_HOME-.}/tests
+# $set_env.py: COVERAGE_CONTEXT - Set to 'test_function' for who-tests-what
+dynamic_context = ${COVERAGE_CONTEXT-none}
+
+[report]
+# We set a different pragmas so our code won't be confused with test code, and
+# we use different pragmas for different reasons that the lines won't be
+# measured.
+exclude_lines =
+ pragma: not covered
+
+ # Lines in test code that aren't covered: we are nested inside ourselves.
+ # Sometimes this is used as a comment:
+ #
+ # cov.start()
+ # blah() # pragma: nested
+ # cov.stop() # pragma: nested
+ #
+ # In order to exclude a series of lines, sometimes it's used as a constant
+ # condition, which might be too cute:
+ #
+ # cov.start()
+ # if "pragma: nested":
+ # blah()
+ # cov.stop()
+ #
+ pragma: nested
+
+ # Lines that are only executed when we are debugging coverage.py.
+ def __repr__
+ pragma: debugging
+
+ # Lines that are only executed when we are not testing coverage.py.
+ pragma: not testing
+
+ # Lines that we can't run during metacov.
+ pragma: no metacov
+
+ # These lines only happen if tests fail.
+ raise AssertionError
+ pragma: only failure
+
+ # OS error conditions that we can't (or don't care to) replicate.
+ pragma: cant happen
+
+ # Obscure bugs in specific versions of interpreters, and so probably no
+ # longer tested.
+ pragma: obscure
+
+ # Jython needs special care.
+ pragma: only jython
+ skip.*Jython
+
+ # IronPython isn't included in metacoverage.
+ pragma: only ironpython
+
+partial_branches =
+ pragma: part covered
+ pragma: if failure
+ pragma: part started
+ if env.TESTING:
+ if .* env.JYTHON
+ if .* env.IRONPYTHON
+
+ignore_errors = true
+precision = 1
+
+[paths]
+source =
+ .
+ *\coverage\trunk
+ */coverage/trunk
+ *\coveragepy
+ /io
diff --git a/third_party/python/coverage/pylintrc b/third_party/python/coverage/pylintrc
new file mode 100644
index 0000000000..d250e9b920
--- /dev/null
+++ b/third_party/python/coverage/pylintrc
@@ -0,0 +1,335 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# lint Python modules using external checkers.
+#
+# This is the main checker controlling the other ones and the reports
+# generation. It is itself both a raw checker and an astng checker in order
+# to:
+# * handle message activation / deactivation at the module level
+# * handle some basic but necessary stats'data (number of classes, methods...)
+#
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=
+
+# Pickle collected data for later comparisons.
+persistent=no
+
+# Set the cache size for astng objects.
+cache-size=500
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+extension-pkg-whitelist=
+ greenlet
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+
+# Enable all messages in the listed categories.
+#enable-msg-cat=
+
+# Disable all messages in the listed categories.
+#disable-msg-cat=
+
+# Enable the message(s) with the given id(s).
+enable=
+ useless-suppression
+
+# Disable the message(s) with the given id(s).
+disable=
+ spelling,
+# Messages that are just silly:
+ locally-disabled,
+ exec-used,
+ no-init,
+ bad-whitespace,
+ global-statement,
+ broad-except,
+ no-else-return,
+# Messages that may be silly:
+ no-self-use,
+ no-member,
+ using-constant-test,
+ too-many-nested-blocks,
+ too-many-ancestors,
+ unnecessary-pass,
+ no-else-break,
+ no-else-continue,
+# Questionable things, but it's ok, I don't need to be told:
+ import-outside-toplevel,
+ self-assigning-variable,
+# Formatting stuff
+ superfluous-parens,
+ bad-continuation,
+# Disable while we still support Python 2:
+ useless-object-inheritance,
+# Messages that are noisy for now, eventually maybe we'll turn them on:
+ invalid-name,
+ protected-access,
+ duplicate-code,
+ cyclic-import
+
+msg-template={path}:{line}: {msg} ({symbol})
+
+[REPORTS]
+
+# set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells wether to display a full report or only the messages
+reports=no
+
+# I don't need a score, thanks.
+score=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note).You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (R0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Enable the report(s) with the given id(s).
+#enable-report=
+
+# Disable the report(s) with the given id(s).
+#disable-report=
+
+
+# checks for :
+# * doc strings
+# * modules / classes / functions / methods / arguments / variables name
+# * number of arguments, local variables, branchs, returns and statements in
+# functions, methods
+# * required module attributes
+# * dangerous default values as arguments
+# * redefinition of function / method / class
+# * uses of the global statement
+#
+[BASIC]
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+# Special methods don't: __foo__
+# Test methods don't: testXXXX
+# TestCase overrides don't: setUp, tearDown
+# Nested decorator implementations: _decorator, _wrapper
+# Dispatched methods don't: _xxx__Xxxx
+no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapper|_.*__.*
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$|setUp|tearDown|test_.*
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=
+
+
+# try to find bugs in the code using type inference
+#
+[TYPECHECK]
+
+# Tells wether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamicaly set).
+ignored-classes=SQLObject
+
+# List of members which are usually get through zope's acquisition mecanism and
+# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
+acquired-members=REQUEST,acl_users,aq_parent
+
+
+# checks for
+# * unused variables / imports
+# * undefined variables
+# * redefinition of variable from builtins or from an outer scope
+# * use of variable before assigment
+#
+[VARIABLES]
+
+# Tells wether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching names of unused arguments.
+ignored-argument-names=_|unused|.*_unused
+dummy-variables-rgx=_|unused|.*_unused
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+# checks for :
+# * methods without self as first argument
+# * overridden methods signature
+# * access only to existent members via self
+# * attributes not defined in the __init__ method
+# * supported interfaces implementation
+# * unreachable code
+#
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp,reset
+
+
+# checks for sign of poor/misdesign:
+# * number of methods, attributes, local variables...
+# * size, complexity of functions, methods
+#
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=15
+
+# Maximum number of locals for function / method body
+max-locals=50
+
+# Maximum number of return / yield for function / method body
+max-returns=20
+
+# Maximum number of branch for function / method body
+max-branches=50
+
+# Maximum number of statements in function / method body
+max-statements=150
+
+# Maximum number of parents for a class (see R0901).
+max-parents=12
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=40
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=500
+
+
+# checks for
+# * external modules dependencies
+# * relative / wildcard imports
+# * cyclic imports
+# * uses of deprecated modules
+#
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report R0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report R0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report R0402 must
+# not be disabled)
+int-import-graph=
+
+
+# checks for :
+# * unauthorized constructions
+# * strict indentation
+# * line length
+# * use of <> instead of !=
+#
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module
+max-module-lines=10000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+# checks for:
+# * warning notes in the code like FIXME, XXX
+# * PEP 263: source code with non ascii character but no encoding declaration
+#
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+# checks for similarities and duplicated code. This computation may be
+# memory / CPU intensive, so you should disable it if you experiments some
+# problems.
+#
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
diff --git a/third_party/python/coverage/requirements/ci.pip b/third_party/python/coverage/requirements/ci.pip
new file mode 100644
index 0000000000..c36045685c
--- /dev/null
+++ b/third_party/python/coverage/requirements/ci.pip
@@ -0,0 +1,8 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# Things CI servers need to succeeed.
+-r tox.pip
+-r pytest.pip
+-r wheel.pip
+tox-travis==0.12
diff --git a/third_party/python/coverage/requirements/dev.pip b/third_party/python/coverage/requirements/dev.pip
new file mode 100644
index 0000000000..a11729cdb2
--- /dev/null
+++ b/third_party/python/coverage/requirements/dev.pip
@@ -0,0 +1,24 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# Requirements for doing local development work on coverage.py.
+# https://requires.io/github/nedbat/coveragepy/requirements/
+
+pip==20.0.2
+virtualenv==16.7.9
+
+pluggy==0.13.1
+
+# PyPI requirements for running tests.
+-r tox.pip
+-r pytest.pip
+
+# for linting.
+greenlet==0.4.15
+pylint==2.4.4
+check-manifest==0.40
+readme_renderer==24.0
+
+# for kitting.
+requests==2.22.0
+twine==2.0.0
diff --git a/third_party/python/coverage/requirements/pytest.pip b/third_party/python/coverage/requirements/pytest.pip
new file mode 100644
index 0000000000..3b5499748e
--- /dev/null
+++ b/third_party/python/coverage/requirements/pytest.pip
@@ -0,0 +1,21 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# The pytest specifics used by coverage.py
+
+# 4.x is last to support py2
+pytest==4.6.6
+pytest-xdist==1.30.0
+flaky==3.6.1
+mock==3.0.5
+# Use a fork of PyContracts that supports Python 3.9
+#PyContracts==1.8.12
+git+https://github.com/slorg1/contracts@collections_and_validator
+hypothesis==4.41.2
+
+# Our testing mixins
+unittest-mixins==1.6
+#-e/Users/ned/unittest_mixins
+
+# Just so I have a debugger if I want it
+pudb==2019.1
diff --git a/third_party/python/coverage/requirements/tox.pip b/third_party/python/coverage/requirements/tox.pip
new file mode 100644
index 0000000000..a6279c325c
--- /dev/null
+++ b/third_party/python/coverage/requirements/tox.pip
@@ -0,0 +1,7 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# The version of tox used by coverage.py
+tox==3.14.3
+# Adds env recreation on requirements file changes.
+tox-battery==0.5.2
diff --git a/third_party/python/coverage/requirements/wheel.pip b/third_party/python/coverage/requirements/wheel.pip
new file mode 100644
index 0000000000..abef9db4d6
--- /dev/null
+++ b/third_party/python/coverage/requirements/wheel.pip
@@ -0,0 +1,7 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+# Things needed to make wheels for coverage.py
+
+setuptools==41.4.0
+wheel==0.34.2
diff --git a/third_party/python/coverage/setup.cfg b/third_party/python/coverage/setup.cfg
new file mode 100644
index 0000000000..e76069e01d
--- /dev/null
+++ b/third_party/python/coverage/setup.cfg
@@ -0,0 +1,19 @@
+[tool:pytest]
+addopts = -q -n3 --strict --no-flaky-report -rfe --failed-first
+markers =
+ expensive: too slow to run during "make smoke"
+filterwarnings =
+ ignore:dns.hash module will be removed:DeprecationWarning
+ ignore:Using or importing the ABCs:DeprecationWarning
+
+[pep8]
+ignore = E265,E266,E123,E133,E226,E241,E242,E301,E401
+max-line-length = 100
+
+[metadata]
+license_file = LICENSE.txt
+
+[egg_info]
+tag_build =
+tag_date = 0
+
diff --git a/third_party/python/coverage/setup.py b/third_party/python/coverage/setup.py
new file mode 100644
index 0000000000..8c837d72cf
--- /dev/null
+++ b/third_party/python/coverage/setup.py
@@ -0,0 +1,217 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Code coverage measurement for Python"""
+
+# Distutils setup for coverage.py
+# This file is used unchanged under all versions of Python, 2.x and 3.x.
+
+import os
+import sys
+
+# Setuptools has to be imported before distutils, or things break.
+from setuptools import setup
+from distutils.core import Extension # pylint: disable=wrong-import-order
+from distutils.command.build_ext import build_ext # pylint: disable=wrong-import-order
+from distutils import errors # pylint: disable=wrong-import-order
+
+
+# Get or massage our metadata. We exec coverage/version.py so we can avoid
+# importing the product code into setup.py.
+
+classifiers = """\
+Environment :: Console
+Intended Audience :: Developers
+License :: OSI Approved :: Apache Software License
+Operating System :: OS Independent
+Programming Language :: Python
+Programming Language :: Python :: 2
+Programming Language :: Python :: 2.7
+Programming Language :: Python :: 3
+Programming Language :: Python :: 3.5
+Programming Language :: Python :: 3.6
+Programming Language :: Python :: 3.7
+Programming Language :: Python :: 3.8
+Programming Language :: Python :: 3.9
+Programming Language :: Python :: Implementation :: CPython
+Programming Language :: Python :: Implementation :: PyPy
+Topic :: Software Development :: Quality Assurance
+Topic :: Software Development :: Testing
+"""
+
+cov_ver_py = os.path.join(os.path.split(__file__)[0], "coverage/version.py")
+with open(cov_ver_py) as version_file:
+ # __doc__ will be overwritten by version.py.
+ doc = __doc__
+ # Keep pylint happy.
+ __version__ = __url__ = version_info = ""
+ # Execute the code in version.py.
+ exec(compile(version_file.read(), cov_ver_py, 'exec'))
+
+with open("README.rst") as readme:
+ long_description = readme.read().replace("https://coverage.readthedocs.io", __url__)
+
+with open("CONTRIBUTORS.txt", "rb") as contributors:
+ paras = contributors.read().split(b"\n\n")
+ num_others = len(paras[-1].splitlines())
+ num_others += 1 # Count Gareth Rees, who is mentioned in the top paragraph.
+
+classifier_list = classifiers.splitlines()
+
+if version_info[3] == 'alpha':
+ devstat = "3 - Alpha"
+elif version_info[3] in ['beta', 'candidate']:
+ devstat = "4 - Beta"
+else:
+ assert version_info[3] == 'final'
+ devstat = "5 - Production/Stable"
+classifier_list.append("Development Status :: " + devstat)
+
+# Create the keyword arguments for setup()
+
+setup_args = dict(
+ name='coverage',
+ version=__version__,
+
+ packages=[
+ 'coverage',
+ ],
+
+ package_data={
+ 'coverage': [
+ 'htmlfiles/*.*',
+ 'fullcoverage/*.*',
+ ]
+ },
+
+ entry_points={
+ # Install a script as "coverage", and as "coverage[23]", and as
+ # "coverage-2.7" (or whatever).
+ 'console_scripts': [
+ 'coverage = coverage.cmdline:main',
+ 'coverage%d = coverage.cmdline:main' % sys.version_info[:1],
+ 'coverage-%d.%d = coverage.cmdline:main' % sys.version_info[:2],
+ ],
+ },
+
+ extras_require={
+ # Enable pyproject.toml support.
+ 'toml': ['toml'],
+ },
+
+ # We need to get HTML assets from our htmlfiles directory.
+ zip_safe=False,
+
+ author='Ned Batchelder and {} others'.format(num_others),
+ author_email='ned@nedbatchelder.com',
+ description=doc,
+ long_description=long_description,
+ long_description_content_type='text/x-rst',
+ keywords='code coverage testing',
+ license='Apache 2.0',
+ classifiers=classifier_list,
+ url="https://github.com/nedbat/coveragepy",
+ project_urls={
+ 'Documentation': __url__,
+ 'Funding': (
+ 'https://tidelift.com/subscription/pkg/pypi-coverage'
+ '?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi'
+ ),
+ 'Issues': 'https://github.com/nedbat/coveragepy/issues',
+ },
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4",
+)
+
+# A replacement for the build_ext command which raises a single exception
+# if the build fails, so we can fallback nicely.
+
+ext_errors = (
+ errors.CCompilerError,
+ errors.DistutilsExecError,
+ errors.DistutilsPlatformError,
+)
+if sys.platform == 'win32':
+ # distutils.msvc9compiler can raise an IOError when failing to
+ # find the compiler
+ ext_errors += (IOError,)
+
+
+class BuildFailed(Exception):
+ """Raise this to indicate the C extension wouldn't build."""
+ def __init__(self):
+ Exception.__init__(self)
+ self.cause = sys.exc_info()[1] # work around py 2/3 different syntax
+
+
+class ve_build_ext(build_ext):
+ """Build C extensions, but fail with a straightforward exception."""
+
+ def run(self):
+ """Wrap `run` with `BuildFailed`."""
+ try:
+ build_ext.run(self)
+ except errors.DistutilsPlatformError:
+ raise BuildFailed()
+
+ def build_extension(self, ext):
+ """Wrap `build_extension` with `BuildFailed`."""
+ try:
+ # Uncomment to test compile failure handling:
+ # raise errors.CCompilerError("OOPS")
+ build_ext.build_extension(self, ext)
+ except ext_errors:
+ raise BuildFailed()
+ except ValueError as err:
+ # this can happen on Windows 64 bit, see Python issue 7511
+ if "'path'" in str(err): # works with both py 2/3
+ raise BuildFailed()
+ raise
+
+# There are a few reasons we might not be able to compile the C extension.
+# Figure out if we should attempt the C extension or not.
+
+compile_extension = True
+
+if sys.platform.startswith('java'):
+ # Jython can't compile C extensions
+ compile_extension = False
+
+if '__pypy__' in sys.builtin_module_names:
+ # Pypy can't compile C extensions
+ compile_extension = False
+
+if compile_extension:
+ setup_args.update(dict(
+ ext_modules=[
+ Extension(
+ "coverage.tracer",
+ sources=[
+ "coverage/ctracer/datastack.c",
+ "coverage/ctracer/filedisp.c",
+ "coverage/ctracer/module.c",
+ "coverage/ctracer/tracer.c",
+ ],
+ ),
+ ],
+ cmdclass={
+ 'build_ext': ve_build_ext,
+ },
+ ))
+
+
+def main():
+ """Actually invoke setup() with the arguments we built above."""
+ # For a variety of reasons, it might not be possible to install the C
+ # extension. Try it with, and if it fails, try it without.
+ try:
+ setup(**setup_args)
+ except BuildFailed as exc:
+ msg = "Couldn't install with extension module, trying without it..."
+ exc_msg = "%s: %s" % (exc.__class__.__name__, exc.cause)
+ print("**\n** %s\n** %s\n**" % (msg, exc_msg))
+
+ del setup_args['ext_modules']
+ setup(**setup_args)
+
+if __name__ == '__main__':
+ main()
diff --git a/third_party/python/coverage/tox.ini b/third_party/python/coverage/tox.ini
new file mode 100644
index 0000000000..57c4d4bca0
--- /dev/null
+++ b/third_party/python/coverage/tox.ini
@@ -0,0 +1,95 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+[tox]
+envlist = py{27,35,36,37,38,39}, pypy{2,3}, doc, lint
+skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True}
+toxworkdir = {env:TOXWORKDIR:.tox}
+
+[testenv]
+usedevelop = True
+extras =
+ toml
+
+deps =
+ # Check here for what might be out of date:
+ # https://requires.io/github/nedbat/coveragepy/requirements/
+ -r requirements/pytest.pip
+ pip==20.0.2
+ setuptools==41.4.0
+ # gevent 1.3 causes a failure: https://github.com/nedbat/coveragepy/issues/663
+ py{27,35,36}: gevent==1.2.2
+ py{27,35,36,37,38}: eventlet==0.25.1
+ py{27,35,36,37,38}: greenlet==0.4.15
+
+# Windows can't update the pip version with pip running, so use Python
+# to install things.
+install_command = python -m pip install -U {opts} {packages}
+
+passenv = *
+setenv =
+ pypy,pypy{2,3}: COVERAGE_NO_CTRACER=no C extension under PyPy
+ jython: COVERAGE_NO_CTRACER=no C extension under Jython
+ jython: PYTEST_ADDOPTS=-n 0
+
+commands =
+ python setup.py --quiet clean develop
+
+ # Create tests/zipmods.zip
+ # Install the egg1 egg
+ # Remove the C extension so that we can test the PyTracer
+ python igor.py zip_mods install_egg remove_extension
+
+ # Test with the PyTracer
+ python igor.py test_with_tracer py {posargs}
+
+ # Build the C extension and test with the CTracer
+ python setup.py --quiet build_ext --inplace
+ python igor.py test_with_tracer c {posargs}
+
+[testenv:py39]
+basepython = python3.9
+
+[testenv:anypy]
+# For running against my own builds of CPython, or any other specific Python.
+basepython = {env:COVERAGE_PYTHON}
+
+[testenv:doc]
+# Build the docs so we know if they are successful. We build twice: once with
+# -q to get all warnings, and once with -QW to get a success/fail status
+# return.
+deps =
+ -r doc/requirements.pip
+commands =
+ python doc/check_copied_from.py doc/*.rst
+ doc8 -q --ignore-path 'doc/_*' doc CHANGES.rst README.rst
+ sphinx-build -b html -aqE doc doc/_build/html
+ rst2html.py --strict README.rst doc/_build/trash
+ - sphinx-build -b html -b linkcheck -aEnq doc doc/_build/html
+ - sphinx-build -b html -b linkcheck -aEnQW doc doc/_build/html
+
+[testenv:lint]
+deps =
+ -r requirements/dev.pip
+ -r doc/requirements.pip
+
+setenv =
+ LINTABLE = coverage tests doc ci igor.py setup.py __main__.py
+
+commands =
+ python -m tabnanny {env:LINTABLE}
+ python igor.py check_eol
+ check-manifest --ignore 'lab*,perf*,doc/sample_html*,.treerc,.github*'
+ python setup.py -q sdist bdist_wheel
+ twine check dist/*
+ python -m pylint --notes= -j 4 {env:LINTABLE}
+
+[travis]
+#2.7: py27, lint
+python =
+ 2.7: py27
+ 3.5: py35
+ 3.6: py36
+ 3.7: py37
+ pypy: pypy
+ pypy3.5: pypy3
diff --git a/third_party/python/coverage/tox_wheels.ini b/third_party/python/coverage/tox_wheels.ini
new file mode 100644
index 0000000000..92a1ddf419
--- /dev/null
+++ b/third_party/python/coverage/tox_wheels.ini
@@ -0,0 +1,21 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+[tox]
+envlist = py{27,35,36,37,38,sys}
+toxworkdir = {toxinidir}/.tox/wheels
+
+[testenv]
+deps =
+ -rrequirements/wheel.pip
+
+commands =
+ python -c "import sys; print(sys.real_prefix)"
+ python setup.py bdist_wheel {posargs}
+
+[testenv:py27]
+basepython = python2.7
+
+[testenv:pysys]
+# For building with the Mac Framework Python.
+basepython = /usr/bin/python