aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/coverage/py3
diff options
context:
space:
mode:
authorvitalyisaev <vitalyisaev@ydb.tech>2023-11-30 13:26:22 +0300
committervitalyisaev <vitalyisaev@ydb.tech>2023-11-30 15:44:45 +0300
commit0a98fece5a9b54f16afeb3a94b3eb3105e9c3962 (patch)
tree291d72dbd7e9865399f668c84d11ed86fb190bbf /contrib/python/coverage/py3
parentcb2c8d75065e5b3c47094067cb4aa407d4813298 (diff)
downloadydb-0a98fece5a9b54f16afeb3a94b3eb3105e9c3962.tar.gz
YQ Connector:Use docker-compose in integrational tests
Diffstat (limited to 'contrib/python/coverage/py3')
-rw-r--r--contrib/python/coverage/py3/.dist-info/METADATA190
-rw-r--r--contrib/python/coverage/py3/.dist-info/entry_points.txt5
-rw-r--r--contrib/python/coverage/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/coverage/py3/LICENSE.txt177
-rw-r--r--contrib/python/coverage/py3/NOTICE.txt14
-rw-r--r--contrib/python/coverage/py3/README.rst151
-rw-r--r--contrib/python/coverage/py3/coverage/__init__.py36
-rw-r--r--contrib/python/coverage/py3/coverage/__main__.py8
-rw-r--r--contrib/python/coverage/py3/coverage/annotate.py108
-rw-r--r--contrib/python/coverage/py3/coverage/backward.py267
-rw-r--r--contrib/python/coverage/py3/coverage/bytecode.py19
-rw-r--r--contrib/python/coverage/py3/coverage/cmdline.py910
-rw-r--r--contrib/python/coverage/py3/coverage/collector.py455
-rw-r--r--contrib/python/coverage/py3/coverage/config.py605
-rw-r--r--contrib/python/coverage/py3/coverage/context.py91
-rw-r--r--contrib/python/coverage/py3/coverage/control.py1162
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/datastack.c50
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/datastack.h45
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/filedisp.c85
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/filedisp.h26
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/module.c108
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/stats.h31
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/tracer.c1149
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/tracer.h75
-rw-r--r--contrib/python/coverage/py3/coverage/ctracer/util.h103
-rw-r--r--contrib/python/coverage/py3/coverage/data.py125
-rw-r--r--contrib/python/coverage/py3/coverage/debug.py406
-rw-r--r--contrib/python/coverage/py3/coverage/disposition.py37
-rw-r--r--contrib/python/coverage/py3/coverage/env.py130
-rw-r--r--contrib/python/coverage/py3/coverage/execfile.py362
-rw-r--r--contrib/python/coverage/py3/coverage/files.py441
-rw-r--r--contrib/python/coverage/py3/coverage/fullcoverage/encodings.py60
-rw-r--r--contrib/python/coverage/py3/coverage/html.py539
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/coverage_html.js616
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/favicon_32.pngbin0 -> 1732 bytes
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/index.html119
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js9
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/jquery.hotkeys.js99
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/jquery.isonscreen.js53
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/jquery.min.js4
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/jquery.tablesorter.min.js2
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/keybd_closed.pngbin0 -> 112 bytes
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/keybd_open.pngbin0 -> 112 bytes
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/pyfile.html113
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/style.css291
-rw-r--r--contrib/python/coverage/py3/coverage/htmlfiles/style.scss660
-rw-r--r--contrib/python/coverage/py3/coverage/inorout.py513
-rw-r--r--contrib/python/coverage/py3/coverage/jsonreport.py103
-rw-r--r--contrib/python/coverage/py3/coverage/misc.py361
-rw-r--r--contrib/python/coverage/py3/coverage/multiproc.py117
-rw-r--r--contrib/python/coverage/py3/coverage/numbits.py163
-rw-r--r--contrib/python/coverage/py3/coverage/parser.py1276
-rw-r--r--contrib/python/coverage/py3/coverage/phystokens.py297
-rw-r--r--contrib/python/coverage/py3/coverage/plugin.py533
-rw-r--r--contrib/python/coverage/py3/coverage/plugin_support.py281
-rw-r--r--contrib/python/coverage/py3/coverage/python.py261
-rw-r--r--contrib/python/coverage/py3/coverage/pytracer.py274
-rw-r--r--contrib/python/coverage/py3/coverage/report.py86
-rw-r--r--contrib/python/coverage/py3/coverage/results.py343
-rw-r--r--contrib/python/coverage/py3/coverage/sqldata.py1123
-rw-r--r--contrib/python/coverage/py3/coverage/summary.py152
-rw-r--r--contrib/python/coverage/py3/coverage/templite.py302
-rw-r--r--contrib/python/coverage/py3/coverage/tomlconfig.py168
-rw-r--r--contrib/python/coverage/py3/coverage/version.py33
-rw-r--r--contrib/python/coverage/py3/coverage/xmlreport.py234
-rw-r--r--contrib/python/coverage/py3/ya.make98
66 files changed, 16655 insertions, 0 deletions
diff --git a/contrib/python/coverage/py3/.dist-info/METADATA b/contrib/python/coverage/py3/.dist-info/METADATA
new file mode 100644
index 0000000000..25a6049c45
--- /dev/null
+++ b/contrib/python/coverage/py3/.dist-info/METADATA
@@ -0,0 +1,190 @@
+Metadata-Version: 2.1
+Name: coverage
+Version: 5.5
+Summary: Code coverage measurement for Python
+Home-page: https://github.com/nedbat/coveragepy
+Author: Ned Batchelder and 142 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
+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 :: 3.10
+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
+Requires-Dist: toml ; extra == 'toml'
+
+.. 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|
+| |test-status| |quality-status| |docs| |codecov|
+| |kit| |format| |repos| |downloads|
+| |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.10 alpha.
+* PyPy2 7.3.3 and PyPy3 7.3.3.
+
+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.x:** 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
+ :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
+
+
+.. |test-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml
+ :alt: Test suite status
+.. |quality-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml
+ :alt: Quality check status
+.. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
+ :target: https://coverage.readthedocs.io/
+ :alt: Documentation
+.. |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/contrib/python/coverage/py3/.dist-info/entry_points.txt b/contrib/python/coverage/py3/.dist-info/entry_points.txt
new file mode 100644
index 0000000000..cd083fc1ff
--- /dev/null
+++ b/contrib/python/coverage/py3/.dist-info/entry_points.txt
@@ -0,0 +1,5 @@
+[console_scripts]
+coverage = coverage.cmdline:main
+coverage-3.9 = coverage.cmdline:main
+coverage3 = coverage.cmdline:main
+
diff --git a/contrib/python/coverage/py3/.dist-info/top_level.txt b/contrib/python/coverage/py3/.dist-info/top_level.txt
new file mode 100644
index 0000000000..4ebc8aea50
--- /dev/null
+++ b/contrib/python/coverage/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+coverage
diff --git a/contrib/python/coverage/py3/LICENSE.txt b/contrib/python/coverage/py3/LICENSE.txt
new file mode 100644
index 0000000000..f433b1a53f
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/NOTICE.txt b/contrib/python/coverage/py3/NOTICE.txt
new file mode 100644
index 0000000000..37ded535bf
--- /dev/null
+++ b/contrib/python/coverage/py3/NOTICE.txt
@@ -0,0 +1,14 @@
+Copyright 2001 Gareth Rees. All rights reserved.
+Copyright 2004-2021 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/contrib/python/coverage/py3/README.rst b/contrib/python/coverage/py3/README.rst
new file mode 100644
index 0000000000..072f30ffeb
--- /dev/null
+++ b/contrib/python/coverage/py3/README.rst
@@ -0,0 +1,151 @@
+.. 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|
+| |test-status| |quality-status| |docs| |codecov|
+| |kit| |format| |repos| |downloads|
+| |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.10 alpha.
+* PyPy2 7.3.3 and PyPy3 7.3.3.
+
+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.x:** 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
+ :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
+
+
+.. |test-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/testsuite.yml
+ :alt: Test suite status
+.. |quality-status| image:: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml/badge.svg?branch=master&event=push
+ :target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml
+ :alt: Quality check status
+.. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
+ :target: https://coverage.readthedocs.io/
+ :alt: Documentation
+.. |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/contrib/python/coverage/py3/coverage/__init__.py b/contrib/python/coverage/py3/coverage/__init__.py
new file mode 100644
index 0000000000..331b304b68
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/__main__.py b/contrib/python/coverage/py3/coverage/__main__.py
new file mode 100644
index 0000000000..79aa4e2b35
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/annotate.py b/contrib/python/coverage/py3/coverage/annotate.py
new file mode 100644
index 0000000000..999ab6e557
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/backward.py b/contrib/python/coverage/py3/coverage/backward.py
new file mode 100644
index 0000000000..ac781ab96a
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/backward.py
@@ -0,0 +1,267 @@
+# 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 datetime import datetime
+
+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: # pragma: not covered
+ # We need this on Python 2, but in testing environments, a backport is
+ # installed, so this import isn't used.
+ import repr as reprlib
+
+# A function to iterate listlessly over a dict's items, and one to get the
+# 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 format_local_datetime(dt):
+ """Return a string with local timezone representing the date.
+ If python version is lower than 3.6, the time zone is not included.
+ """
+ try:
+ return dt.astimezone().strftime('%Y-%m-%d %H:%M %z')
+ except (TypeError, ValueError):
+ # Datetime.astimezone in Python 3.5 can not handle naive datetime
+ return dt.strftime('%Y-%m-%d %H:%M')
+
+
+def invalidate_import_caches():
+ """Invalidate any import caches that may or may not exist."""
+ if importlib and hasattr(importlib, "invalidate_caches"):
+ 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:
+ import importlib.util as importlib_util
+ except ImportError:
+ importlib_util = None
+
+ if modfile is None:
+ modfile = modname + '.py'
+ if importlib_util:
+ spec = importlib_util.spec_from_file_location(modname, modfile)
+ mod = importlib_util.module_from_spec(spec)
+ sys.modules[modname] = mod
+ spec.loader.exec_module(mod)
+ else:
+ for suff in imp.get_suffixes(): # pragma: part covered
+ if suff[0] == '.py':
+ 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/contrib/python/coverage/py3/coverage/bytecode.py b/contrib/python/coverage/py3/coverage/bytecode.py
new file mode 100644
index 0000000000..ceb18cf374
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/cmdline.py b/contrib/python/coverage/py3/coverage/cmdline.py
new file mode 100644
index 0000000000..0be0cca19f
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/cmdline.py
@@ -0,0 +1,910 @@
+# 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.",
+ )
+ keep = optparse.make_option(
+ '', '--keep', action='store_true',
+ help="Keep original coverage files, otherwise they are deleted.",
+ )
+ branch = optparse.make_option(
+ '', '--branch', action='store_true',
+ help="Measure branch coverage in addition to statement coverage.",
+ )
+ 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."
+ ),
+ )
+ sort = optparse.make_option(
+ '--sort', action='store', metavar='COLUMN',
+ help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. "
+ "Default is name."
+ )
+ show_missing = optparse.make_option(
+ '-m', '--show-missing', action='store_true',
+ help="Show line numbers of statements in each module that weren't executed.",
+ )
+ skip_covered = optparse.make_option(
+ '--skip-covered', action='store_true',
+ help="Skip files with 100% coverage.",
+ )
+ no_skip_covered = optparse.make_option(
+ '--no-skip-covered', action='store_false', dest='skip_covered',
+ help="Disable --skip-covered.",
+ )
+ skip_empty = optparse.make_option(
+ '--skip-empty', action='store_true',
+ help="Skip files with no code.",
+ )
+ 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."
+ ),
+ )
+ precision = optparse.make_option(
+ '', '--precision', action='store', metavar='N', type=int,
+ help=(
+ "Number of digits after the decimal point to display for "
+ "reported coverage percentages."
+ ),
+ )
+ rcfile = optparse.make_option(
+ '', '--rcfile', action='store',
+ help=(
+ "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,
+ keep=None,
+ module=None,
+ omit=None,
+ contexts=None,
+ parallel_mode=None,
+ precision=None,
+ pylib=None,
+ rcfile=True,
+ show_missing=None,
+ skip_covered=None,
+ skip_empty=None,
+ show_contexts=None,
+ sort=None,
+ source=None,
+ timid=None,
+ title=None,
+ 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,
+ Opts.keep,
+ ] + 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 about the internals of coverage.py, "
+ "for diagnosing problems. "
+ "Topics are: "
+ "'data' to show a summary of the collected data; "
+ "'sys' to show installation information; "
+ "'config' to show the configuration; "
+ "'premain' to show what is calling coverage."
+ ),
+ ),
+
+ '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.precision,
+ Opts.show_contexts,
+ Opts.skip_covered,
+ Opts.no_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.precision,
+ Opts.sort,
+ Opts.show_missing,
+ Opts.skip_covered,
+ Opts.no_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,
+ Opts.skip_empty,
+ ] + 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, keep=bool(options.keep))
+ 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,
+ precision=options.precision,
+ sort=options.sort,
+ **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,
+ precision=options.precision,
+ **report_args
+ )
+ elif options.action == "xml":
+ outfile = options.outfile
+ total = self.coverage.xml_report(
+ outfile=outfile, skip_empty=options.skip_empty,
+ **report_args
+ )
+ elif options.action == "json":
+ outfile = options.outfile
+ total = self.coverage.json_report(
+ 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):
+ msg = "total of {total:.{p}f} is less than fail-under={fail_under:.{p}f}".format(
+ total=total, fail_under=fail_under, p=precision,
+ )
+ print("Coverage failure:", msg)
+ return FAIL_UNDER
+
+ return OK
+
+ 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" % 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.
+ debug Display information about the internals of coverage.py
+ erase Erase previously collected coverage data.
+ help Get help on using coverage.py.
+ html Create an HTML report.
+ 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, version {__version__} {extension_modifier}. 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."""
+ profiler = SimpleLauncher.launch()
+ try:
+ 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/contrib/python/coverage/py3/coverage/collector.py b/contrib/python/coverage/py3/coverage/collector.py
new file mode 100644
index 0000000000..c42d29feec
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/collector.py
@@ -0,0 +1,455 @@
+# 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 = {"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 = {}
+
+ self.disabled_plugins = set()
+
+ # The .should_trace_cache attribute is a cache from file names to
+ # coverage.FileDisposition objects, or None. When a file is first
+ # considered for tracing, a FileDisposition is obtained from
+ # 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
+ if hasattr(tracer, 'disable_plugin'):
+ tracer.disable_plugin = self.disable_plugin
+
+ 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 disable_plugin(self, disposition):
+ """Disable the plugin mentioned in `disposition`."""
+ file_tracer = disposition.file_tracer
+ plugin = file_tracer._coverage_plugin
+ plugin_name = plugin._coverage_plugin_name
+ self.warn("Disabling plug-in {!r} due to previous exception".format(plugin_name))
+ plugin._coverage_enabled = False
+ disposition.trace = False
+
+ def cached_mapped_file(self, filename):
+ """A locally cached version of file names mapped through file_mapper."""
+ key = (type(filename), filename)
+ 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
+
+ if getattr(sys, 'is_standalone_binary', False):
+ # filenames should stay relative to the arcadia root, because files may not exist
+ return dict((k, v) for k, v in items if v)
+
+ return dict((self.cached_mapped_file(k), v) for k, v in items if v)
+
+ def plugin_was_disabled(self, plugin):
+ """Record that `plugin` was disabled during the run."""
+ self.disabled_plugins.add(plugin._coverage_plugin_name)
+
+ def flush_data(self):
+ """Save the collected data to our associated `CoverageData`.
+
+ 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))
+
+ file_tracers = {
+ k: v for k, v in self.file_tracers.items()
+ if v not in self.disabled_plugins
+ }
+ self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers))
+
+ self._clear_data()
+ return True
diff --git a/contrib/python/coverage/py3/coverage/config.py b/contrib/python/coverage/py3/coverage/config.py
new file mode 100644
index 0000000000..ceb7201b65
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/config.py
@@ -0,0 +1,605 @@
+# 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
+import sys
+
+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(section)
+
+ 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):
+ """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(option, section)
+
+ 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.source_pkgs = []
+ 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
+ self.sort = None
+
+ # Defaults for [html]
+ self.extra_css = None
+ self.html_dir = "htmlcov"
+ self.html_skip_covered = None
+ self.html_skip_empty = None
+ self.html_title = "Coverage report"
+ self.show_contexts = False
+
+ # 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 = {}
+ self.suppress_plugin_errors = True
+
+ 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)
+
+ def from_resource(self, resource_name):
+ assert getattr(sys, 'is_standalone_binary', False), 'You have used method by mistake in script, not binary'
+ cp, self._config_contents = _load_config_from_resource(resource_name)
+ return self._parse_config(cp, resource_name, True)
+
+ @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))
+
+ return self._parse_config(cp, filename, our_file)
+
+ def _parse_config(self, cp, filename, our_file):
+ 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)
+ if not getattr(sys, 'is_standalone_binary', False):
+ with open(filename, "rb") 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'),
+ ('source_pkgs', 'run:source_pkgs', 'list'),
+ ('timid', 'run:timid', 'boolean'),
+ ('_crash', 'run:_crash'),
+ ('suppress_plugin_errors', 'run:suppress_plugin_errors', 'boolean'),
+
+ # [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_skip_covered', 'html:skip_covered', 'boolean'),
+ ('html_skip_empty', 'html:skip_empty', 'boolean'),
+ ('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 post_process_file(self, path):
+ """Make final adjustments to a file path to make it usable."""
+ return os.path.expanduser(path)
+
+ def post_process(self):
+ """Make final adjustments to settings to make them usable."""
+ self.data_file = self.post_process_file(self.data_file)
+ self.html_dir = self.post_process_file(self.html_dir)
+ self.xml_output = self.post_process_file(self.xml_output)
+ self.paths = collections.OrderedDict(
+ (k, [self.post_process_file(f) for f in v])
+ for k, v in self.paths.items()
+ )
+
+
+def config_files_to_try(config_file):
+ """What config files should we try to read?
+
+ 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()
+
+ # 1.1 built-in config
+ if getattr(sys, 'is_standalone_binary', False):
+ config.from_resource("/coverage_plugins/coveragerc.txt")
+
+ # 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:
+ if getattr(sys, 'is_standalone_binary', False) and fname == "/coverage_plugins/coveragerc.txt":
+ continue
+ 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.post_process()
+
+ return config
+
+
+def _load_config_from_resource(resource_name):
+ from io import StringIO
+ from library.python import resource
+
+ config_data = resource.find(resource_name)
+ if config_data is None:
+ raise IOError("No such resource: " + resource_name)
+
+ config_data = config_data.decode('utf-8')
+ cp = HandyConfigParser(True)
+ try:
+ cp.readfp(StringIO(config_data))
+ except configparser.Error as err:
+ raise CoverageException("Couldn't read config %s: %s" % (resource_name, err))
+ return cp, config_data
diff --git a/contrib/python/coverage/py3/coverage/context.py b/contrib/python/coverage/py3/coverage/context.py
new file mode 100644
index 0000000000..ea13da21ed
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/control.py b/contrib/python/coverage/py3/coverage/control.py
new file mode 100644
index 0000000000..605b50c26b
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/control.py
@@ -0,0 +1,1162 @@
+# 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 collections
+import contextlib
+import os
+import os.path
+import platform
+import sys
+import time
+import json
+
+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, canonical_filename, 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, source_pkgs=None, omit=None, include=None, debug=None,
+ concurrency=None, check_preimported=False, context=None,
+ ): # pylint: disable=too-many-arguments
+ """
+ Many of these arguments duplicate and override values that can be
+ provided in a configuration file. Parameters that are missing here
+ 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.
+
+ `source_pkgs` is a list of package names. It works the same as
+ `source`, but can be used to name packages where the name can also be
+ interpreted as a file path.
+
+ `include` and `omit` are lists of file name patterns. Files that match
+ `include` will be measured, files that match `omit` will not. Each
+ will also accept a single string argument.
+
+ `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.
+
+ .. versionadded:: 5.3
+ The `source_pkgs` parameter.
+
+ """
+ # data_file=None means no disk file at all. data_file missing means
+ # use the value from the config file.
+ 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, source_pkgs=source_pkgs, 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._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()
+
+ # Store constructor args to reproduce Coverage object in a subprocess created via multiprocessing.Process
+ self._dumped_args = json.dumps(dict(
+ data_file=data_file, data_suffix=data_suffix, cover_pylib=cover_pylib,
+ auto_data=auto_data, timid=timid, branch=branch, config_file=config_file,
+ source=source, omit=omit, include=include, debug=debug,
+ concurrency=concurrency
+ ))
+
+ 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()
+
+ if getattr(sys, 'is_standalone_binary', False):
+ self._file_mapper = canonical_filename
+ else:
+ 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, coverage_args=self._dumped_args)
+
+ 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 = InOrOut(
+ warn=self._warn,
+ debug=(self._debug if self._debug.should('trace') else None),
+ )
+ self._inorout.configure(self.config)
+ self._inorout.plugins = self._plugins
+ self._inorout.disp_class = self._collector.file_disposition_class
+
+ # 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, keep=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.
+
+ If `keep` is true, then original input data files won't be deleted.
+
+ .. versionadded:: 4.0
+ The `data_paths` parameter.
+
+ .. versionadded:: 4.3
+ The `strict` parameter.
+
+ .. versionadded: 5.5
+ The `keep` parameter.
+ """
+ self._init()
+ self._init_data(suffix=None)
+ 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,
+ keep=keep,
+ )
+
+ 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()
+
+ for plugin in self._plugins:
+ if not plugin._coverage_enabled:
+ self._collector.plugin_was_disabled(plugin)
+
+ if self._collector and self._collector.flush_data():
+ self._post_save_work()
+
+ 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:
+ file_paths = collections.defaultdict(list)
+ for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files():
+ file_path = self._file_mapper(file_path)
+ file_paths[plugin_name].append(file_path)
+ for plugin_name, paths in file_paths.items():
+ self._data.touch_files(paths, 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):
+ if getattr(sys, 'is_standalone_binary', False):
+ # Leave morf in canonical format - relative to the arcadia root
+ mapped_morf = morf
+ else:
+ 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, precision=None, sort=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.
+
+ `precision` is the number of digits to display after the decimal
+ point for percentages.
+
+ All of the arguments default to the settings read from the
+ :ref:`configuration file <config>`.
+
+ Returns a float, the total percentage covered.
+
+ .. versionadded:: 4.0
+ The `skip_covered` parameter.
+
+ .. versionadded:: 5.0
+ The `contexts` and `skip_empty` parameters.
+
+ .. versionadded:: 5.2
+ The `precision` parameter.
+
+ """
+ with override_config(
+ self,
+ ignore_errors=ignore_errors, report_omit=omit, report_include=include,
+ show_missing=show_missing, skip_covered=skip_covered,
+ report_contexts=contexts, skip_empty=skip_empty, precision=precision,
+ sort=sort
+ ):
+ 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, precision=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,
+ html_skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts,
+ html_skip_empty=skip_empty, precision=precision,
+ ):
+ reporter = HtmlReporter(self)
+ return reporter.report(morfs)
+
+ def xml_report(
+ self, morfs=None, outfile=None, ignore_errors=None,
+ omit=None, include=None, contexts=None, skip_empty=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, skip_empty=skip_empty,
+ ):
+ 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://github.com/nedbat/coveragepy/issues/340 has more details.
+
+ if hasattr(process_startup, "coverage"):
+ # We've annotated this function before, so we must have already
+ # 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/contrib/python/coverage/py3/coverage/ctracer/datastack.c b/contrib/python/coverage/py3/coverage/ctracer/datastack.c
new file mode 100644
index 0000000000..a9cfcc2cf2
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/datastack.h b/contrib/python/coverage/py3/coverage/ctracer/datastack.h
new file mode 100644
index 0000000000..3b3078ba27
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/filedisp.c b/contrib/python/coverage/py3/coverage/ctracer/filedisp.c
new file mode 100644
index 0000000000..47782ae090
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/filedisp.h b/contrib/python/coverage/py3/coverage/ctracer/filedisp.h
new file mode 100644
index 0000000000..860f9a50b1
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/module.c b/contrib/python/coverage/py3/coverage/ctracer/module.c
new file mode 100644
index 0000000000..f308902b69
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/stats.h b/contrib/python/coverage/py3/coverage/ctracer/stats.h
new file mode 100644
index 0000000000..05173369f7
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/ctracer/tracer.c b/contrib/python/coverage/py3/coverage/ctracer/tracer.c
new file mode 100644
index 0000000000..3e776958cd
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/ctracer/tracer.c
@@ -0,0 +1,1149 @@
+/* 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);
+ Py_XDECREF(self->disable_plugin);
+
+ 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, PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->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 = MyFrame_GetCode(frame)->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, PyFrame_GetLineNumber(frame), 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, PyFrame_GetLineNumber(frame), 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 (MyFrame_GetLasti(frame) < 0) {
+ self->pcur_entry->last_line = -MyFrame_GetCode(frame)->co_firstlineno;
+ }
+ else {
+ self->pcur_entry->last_line = PyFrame_GetLineNumber(frame);
+ }
+
+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 * ret;
+ PyErr_Print();
+
+ STATS( self->stats.pycalls++; )
+ ret = PyObject_CallFunctionObjArgs(self->disable_plugin, disposition, NULL);
+ if (ret == NULL) {
+ goto error;
+ }
+ Py_DECREF(ret);
+
+ return;
+
+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();
+}
+
+
+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, PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->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) {
+ CTracer_disable_plugin(self, self->pcur_entry->disposition);
+ goto ok;
+ }
+ 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 = PyFrame_GetLineNumber(frame);
+ }
+
+ 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 = MyCode_GetCode(MyFrame_GetCode(frame));
+ int lasti = MyFrame_GetLasti(frame);
+
+ if (lasti < MyBytes_GET_SIZE(pCode)) {
+ bytecode = MyBytes_AS_STRING(pCode)[lasti];
+ }
+ if (bytecode != YIELD_VALUE) {
+ int first = MyFrame_GetCode(frame)->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, PyFrame_GetLineNumber(frame), MyFrame_GetCode(frame)->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 = MyFrame_GetCode(frame)->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(MyFrame_GetCode(frame)->co_filename);
+ printf("trace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), PyFrame_GetLineNumber(frame));
+ Py_DECREF(ascii);
+ }
+ #endif
+
+ #if TRACE_LOG
+ ascii = MyText_AS_BYTES(MyFrame_GetCode(frame)->co_filename);
+ if (strstr(MyBytes_AS_STRING(ascii), start_file) && PyFrame_GetLineNumber(frame) == 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(MyFrame_GetCode(frame)->co_filename);
+ printf("pytrace: %s @ %s %d\n", what_sym[what], MyBytes_AS_STRING(ascii), PyFrame_GetLineNumber(frame));
+ 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.") },
+
+ { "disable_plugin", T_OBJECT, offsetof(CTracer, disable_plugin), 0,
+ PyDoc_STR("Function for disabling a plugin.") },
+
+ { 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/contrib/python/coverage/py3/coverage/ctracer/tracer.h b/contrib/python/coverage/py3/coverage/ctracer/tracer.h
new file mode 100644
index 0000000000..8994a9e3d6
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/ctracer/tracer.h
@@ -0,0 +1,75 @@
+/* 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;
+ PyObject * disable_plugin;
+
+ /* 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/contrib/python/coverage/py3/coverage/ctracer/util.h b/contrib/python/coverage/py3/coverage/ctracer/util.h
new file mode 100644
index 0000000000..973672db02
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/ctracer/util.h
@@ -0,0 +1,103 @@
+/* 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. */
+
+#if PY_VERSION_HEX >= 0x030B00A0
+// 3.11 moved f_lasti into an internal structure. This is totally the wrong way
+// to make this work, but it's all I've got until https://bugs.python.org/issue40421
+// is resolved.
+#include <internal/pycore_frame.h>
+#if PY_VERSION_HEX >= 0x030B00A7
+#define MyFrame_GetLasti(f) (PyFrame_GetLasti(f))
+#else
+#define MyFrame_GetLasti(f) ((f)->f_frame->f_lasti * 2)
+#endif
+#elif PY_VERSION_HEX >= 0x030A00A7
+// The f_lasti field changed meaning in 3.10.0a7. It had been bytes, but
+// now is instructions, so we need to adjust it to use it as a byte index.
+#define MyFrame_GetLasti(f) ((f)->f_lasti * 2)
+#else
+#define MyFrame_GetLasti(f) ((f)->f_lasti)
+#endif
+
+// Access f_code should be done through a helper starting in 3.9.
+#if PY_VERSION_HEX >= 0x03090000
+#define MyFrame_GetCode(f) (PyFrame_GetCode(f))
+#else
+#define MyFrame_GetCode(f) ((f)->f_code)
+#endif
+
+#if PY_VERSION_HEX >= 0x030B00B1
+#define MyCode_GetCode(co) (PyCode_GetCode(co))
+#define MyCode_FreeCode(code) Py_XDECREF(code)
+#elif PY_VERSION_HEX >= 0x030B00A7
+#define MyCode_GetCode(co) (PyObject_GetAttrString((PyObject *)(co), "co_code"))
+#define MyCode_FreeCode(code) Py_XDECREF(code)
+#else
+#define MyCode_GetCode(co) ((co)->co_code)
+#define MyCode_FreeCode(code)
+#endif
+
+/* 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/contrib/python/coverage/py3/coverage/data.py b/contrib/python/coverage/py3/coverage/data.py
new file mode 100644
index 0000000000..5dd1dfe3f0
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/data.py
@@ -0,0 +1,125 @@
+# 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, keep=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.
+
+ Unless `keep` is True every data file found and combined is then deleted from disk. If a file
+ cannot be read, a warning will be issued, and the file will not be
+ deleted.
+
+ 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 not keep:
+ if data._debug.should('dataio'):
+ data._debug.write("Deleting combined data file %r" % (f,))
+ file_be_gone(f)
+
+ if strict and not files_combined:
+ raise CoverageException("No usable data files")
diff --git a/contrib/python/coverage/py3/coverage/debug.py b/contrib/python/coverage/py3/coverage/debug.py
new file mode 100644
index 0000000000..194f16f50d
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/disposition.py b/contrib/python/coverage/py3/coverage/disposition.py
new file mode 100644
index 0000000000..9b9a997d8a
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/env.py b/contrib/python/coverage/py3/coverage/env.py
new file mode 100644
index 0000000000..ea78a5be89
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/env.py
@@ -0,0 +1,130 @@
+# 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 implementations.
+CPYTHON = (platform.python_implementation() == "CPython")
+PYPY = (platform.python_implementation() == "PyPy")
+JYTHON = (platform.python_implementation() == "Jython")
+IRONPYTHON = (platform.python_implementation() == "IronPython")
+
+# Python versions. We amend version_info with one more value, a zero if an
+# official version, or 1 if built from source beyond an official version.
+PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)
+PY2 = PYVERSION < (3, 0)
+PY3 = PYVERSION >= (3, 0)
+
+if PYPY:
+ PYPYVERSION = sys.pypy_version_info
+
+PYPY2 = PYPY and PY2
+PYPY3 = PYPY and PY3
+
+# Python behavior.
+class PYBEHAVIOR(object):
+ """Flags indicating this Python's behavior."""
+
+ pep626 = CPYTHON and (PYVERSION > (3, 10, 0, 'alpha', 4))
+
+ # Is "if __debug__" optimized away?
+ if PYPY3:
+ optimize_if_debug = True
+ elif PYPY2:
+ optimize_if_debug = False
+ else:
+ optimize_if_debug = not pep626
+
+ # Is "if not __debug__" optimized away?
+ optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4))
+ if pep626:
+ optimize_if_not_debug = False
+ if PYPY3:
+ optimize_if_not_debug = True
+
+ # Is "if not __debug__" optimized away even better?
+ optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1))
+ if pep626:
+ optimize_if_not_debug2 = False
+
+ # Do we have yield-from?
+ yield_from = (PYVERSION >= (3, 3))
+
+ # 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 = CPYTHON and (PYVERSION >= (3, 7, 0, 'beta', 3))
+
+ # 3.7 changed how functions with only docstrings are numbered.
+ docstring_only_function = (not PYPY) and ((3, 7, 0, 'beta', 5) <= PYVERSION <= (3, 10))
+
+ # When a break/continue/return statement in a try block jumps to a finally
+ # block, does the finally block do the break/continue/return (pre-3.8), or
+ # does the finally jump back to the break/continue/return (3.8) to do the
+ # work?
+ finally_jumps_back = ((3, 8) <= PYVERSION < (3, 10))
+
+ # When a function is decorated, does the trace function get called for the
+ # @-line and also the def-line (new behavior in 3.8)? Or just the @-line
+ # (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))
+
+ # Lines after break/continue/return/raise are no longer compiled into the
+ # bytecode. They used to be marked as missing, now they aren't executable.
+ omit_after_jump = pep626
+
+ # PyPy has always omitted statements after return.
+ omit_after_return = omit_after_jump or PYPY
+
+ # Modules used to have firstlineno equal to the line number of the first
+ # real line of code. Now they always start at 1.
+ module_firstline_1 = pep626
+
+ # Are "if 0:" lines (and similar) kept in the compiled code?
+ keep_constant_test = pep626
+
+# Coverage.py specifics.
+
+# Are we using the C-implemented trace function?
+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/contrib/python/coverage/py3/coverage/execfile.py b/contrib/python/coverage/py3/coverage/execfile.py
new file mode 100644
index 0000000000..29409d517a
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/files.py b/contrib/python/coverage/py3/coverage/files.py
new file mode 100644
index 0000000000..5133ad07f3
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/files.py
@@ -0,0 +1,441 @@
+# 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]
+
+if getattr(sys, 'is_standalone_binary', False):
+ # filename for py files in binary is already canonical,
+ # it's relative to the arcadia root
+ def canonical_filename(filename):
+ # next assert does not needed in case when we load coverage from not arcadia source in arcadia binary
+ # assert not filename.startswith("/"), filename
+ return 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.
+
+ """
+ pattern_sep = sep(pattern)
+
+ if len(pattern) > 1:
+ pattern = pattern.rstrip(r"\/")
+
+ # The pattern can't end with a wildcard component.
+ if pattern.endswith("*"):
+ raise CoverageException("Pattern must not end with wildcards.")
+
+ # 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_sep):
+ 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/contrib/python/coverage/py3/coverage/fullcoverage/encodings.py b/contrib/python/coverage/py3/coverage/fullcoverage/encodings.py
new file mode 100644
index 0000000000..aeb416e406
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/html.py b/contrib/python/coverage/py3/coverage/html.py
new file mode 100644
index 0000000000..9d8e342716
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/html.py
@@ -0,0 +1,539 @@
+# 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 sys
+
+import coverage
+from coverage import env
+from coverage.backward import iitems, SimpleNamespace, format_local_datetime
+from coverage.data import add_data_to_hash
+from coverage.files import flat_rootname
+from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module
+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 get_htmlfiles_resource(name):
+ import pkgutil
+ return pkgutil.get_data(__package__, 'htmlfiles/' + name)
+
+
+def read_data(fname):
+ """Return the contents of a data file of ours."""
+ if getattr(sys, 'is_standalone_binary', False):
+ res_buf = get_htmlfiles_resource(fname).decode()
+ if res_buf is not None:
+ return res_buf
+
+ 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() == {""}:
+ 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", ""),
+ ("favicon_32.png", ""),
+ ]
+
+ def __init__(self, cov):
+ self.coverage = cov
+ self.config = self.coverage.config
+ self.directory = self.config.html_dir
+
+ self.skip_covered = self.config.html_skip_covered
+ if self.skip_covered is None:
+ self.skip_covered = self.config.skip_covered
+ self.skip_empty = self.config.html_skip_empty
+ if self.skip_empty is None:
+ self.skip_empty= self.config.skip_empty
+
+ title = self.config.html_title
+ if env.PY2:
+ title = title.decode("utf8")
+
+ 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': format_local_datetime(datetime.datetime.now()),
+ '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:
+ if getattr(sys, 'is_standalone_binary', False):
+ data = get_htmlfiles_resource(static)
+ if data is None:
+ raise IOError("No such resource: " + static)
+
+ with open(os.path.join(self.directory, static), "wb") as afile:
+ afile.write(data)
+ else:
+ 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.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.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/contrib/python/coverage/py3/coverage/htmlfiles/coverage_html.js b/contrib/python/coverage/py3/coverage/htmlfiles/coverage_html.js
new file mode 100644
index 0000000000..27b49b36f9
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/coverage_html.js
@@ -0,0 +1,616 @@
+// 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).on("unload", function () {
+ try {
+ localStorage.setItem(storage_name, sort_list.toString())
+ } catch(err) {}
+ });
+};
+
+// -- pyfile stuff --
+
+coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
+
+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.filters = undefined;
+ try {
+ coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
+ } catch(err) {}
+
+ if (coverage.filters) {
+ coverage.filters = JSON.parse(coverage.filters);
+ }
+ else {
+ coverage.filters = {run: false, exc: true, mis: true, par: true};
+ }
+
+ for (cls in coverage.filters) {
+ coverage.set_line_visibilty(cls, coverage.filters[cls]);
+ }
+
+ 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) {
+ var onoff = !$(btn).hasClass("show_" + cls);
+ coverage.set_line_visibilty(cls, onoff);
+ coverage.build_scroll_markers();
+ coverage.filters[cls] = onoff;
+ try {
+ localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
+ } catch(err) {}
+};
+
+coverage.set_line_visibilty = function (cls, onoff) {
+ var show = "show_" + cls;
+ var btn = $(".button_toggle_" + cls);
+ if (onoff) {
+ $("#source ." + cls).addClass(show);
+ btn.addClass(show);
+ }
+ else {
+ $("#source ." + cls).removeClass(show);
+ btn.removeClass(show);
+ }
+};
+
+// 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/contrib/python/coverage/py3/coverage/htmlfiles/favicon_32.png b/contrib/python/coverage/py3/coverage/htmlfiles/favicon_32.png
new file mode 100644
index 0000000000..8649f0475d
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/favicon_32.png
Binary files differ
diff --git a/contrib/python/coverage/py3/coverage/htmlfiles/index.html b/contrib/python/coverage/py3/coverage/htmlfiles/index.html
new file mode 100644
index 0000000000..983db06125
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/index.html
@@ -0,0 +1,119 @@
+{# 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="icon" sizes="32x32" href="favicon_32.png">
+ <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/contrib/python/coverage/py3/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js b/contrib/python/coverage/py3/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js
new file mode 100644
index 0000000000..648fe5d3c2
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/htmlfiles/jquery.hotkeys.js b/contrib/python/coverage/py3/coverage/htmlfiles/jquery.hotkeys.js
new file mode 100644
index 0000000000..09b21e03c7
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/htmlfiles/jquery.isonscreen.js b/contrib/python/coverage/py3/coverage/htmlfiles/jquery.isonscreen.js
new file mode 100644
index 0000000000..0182ebd213
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/htmlfiles/jquery.min.js b/contrib/python/coverage/py3/coverage/htmlfiles/jquery.min.js
new file mode 100644
index 0000000000..d1608e37ff
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/htmlfiles/jquery.tablesorter.min.js b/contrib/python/coverage/py3/coverage/htmlfiles/jquery.tablesorter.min.js
new file mode 100644
index 0000000000..64c7007129
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/htmlfiles/keybd_closed.png b/contrib/python/coverage/py3/coverage/htmlfiles/keybd_closed.png
new file mode 100644
index 0000000000..db114023f0
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/keybd_closed.png
Binary files differ
diff --git a/contrib/python/coverage/py3/coverage/htmlfiles/keybd_open.png b/contrib/python/coverage/py3/coverage/htmlfiles/keybd_open.png
new file mode 100644
index 0000000000..db114023f0
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/keybd_open.png
Binary files differ
diff --git a/contrib/python/coverage/py3/coverage/htmlfiles/pyfile.html b/contrib/python/coverage/py3/coverage/htmlfiles/pyfile.html
new file mode 100644
index 0000000000..e15be066fb
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/pyfile.html
@@ -0,0 +1,113 @@
+{# 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="icon" sizes="32x32" href="favicon_32.png">
+ <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;
+ <button type="button" class="{{category.run}} shortkey_r button_toggle_run" title="Toggle lines run">{{nums.n_executed}} run</button>
+ <button type="button" class="{{category.mis}} shortkey_m button_toggle_mis" title="Toggle lines missing">{{nums.n_missing}} missing</button>
+ <button type="button" class="{{category.exc}} shortkey_x button_toggle_exc" title="Toggle lines excluded">{{nums.n_excluded}} excluded</button>
+
+ {% if has_arcs %}
+ <button type="button" class="{{category.par}} shortkey_p button_toggle_par" title="Toggle lines partially run">{{nums.n_partial_branches}} partial</button>
+ {% 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/contrib/python/coverage/py3/coverage/htmlfiles/style.css b/contrib/python/coverage/py3/coverage/htmlfiles/style.css
new file mode 100644
index 0000000000..36ee2a6e65
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/style.css
@@ -0,0 +1,291 @@
+@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: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; }
+
+@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } }
+
+@media (prefers-color-scheme: dark) { body { color: #eee; } }
+
+html > body { font-size: 16px; }
+
+a:active, a:focus { outline: 2px dashed #007acc; }
+
+p { font-size: .875em; line-height: 1.4em; }
+
+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; }
+
+@media (prefers-color-scheme: dark) { #header { background: black; } }
+
+@media (prefers-color-scheme: dark) { #header { border-color: #333; } }
+
+.indexfile #footer { margin: 1rem 3.5rem; }
+
+.pyfile #footer { margin: 1rem 1rem; }
+
+#footer .content { padding: 0; color: #666; font-style: italic; }
+
+@media (prefers-color-scheme: dark) { #footer .content { color: #aaa; } }
+
+#index { margin: 1rem 0 0 3.5rem; }
+
+#header .content { padding: 1rem 3.5rem; }
+
+h1 { font-size: 1.25em; display: inline-block; }
+
+#filter_container { float: right; margin: 0 2em 0 0; }
+
+#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; }
+
+@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } }
+
+@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } }
+
+@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } }
+
+#filter_container input:focus { border-color: #007acc; }
+
+h2.stats { margin-top: .5em; font-size: 1em; }
+
+.stats button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }
+
+@media (prefers-color-scheme: dark) { .stats button { border-color: #444; } }
+
+.stats button:active, .stats button:focus { outline: 2px dashed #007acc; }
+
+.stats button:active, .stats button:focus { outline: 2px dashed #007acc; }
+
+.stats button.run { background: #eeffee; }
+
+@media (prefers-color-scheme: dark) { .stats button.run { background: #373d29; } }
+
+.stats button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; }
+
+@media (prefers-color-scheme: dark) { .stats button.run.show_run { background: #373d29; } }
+
+.stats button.mis { background: #ffeeee; }
+
+@media (prefers-color-scheme: dark) { .stats button.mis { background: #4b1818; } }
+
+.stats button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; }
+
+@media (prefers-color-scheme: dark) { .stats button.mis.show_mis { background: #4b1818; } }
+
+.stats button.exc { background: #f7f7f7; }
+
+@media (prefers-color-scheme: dark) { .stats button.exc { background: #333; } }
+
+.stats button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; }
+
+@media (prefers-color-scheme: dark) { .stats button.exc.show_exc { background: #333; } }
+
+.stats button.par { background: #ffffd5; }
+
+@media (prefers-color-scheme: dark) { .stats button.par { background: #650; } }
+
+.stats button.par.show_par { background: #ffa; border: 2px solid #dddd00; margin: 0 .1em; }
+
+@media (prefers-color-scheme: dark) { .stats button.par.show_par { background: #650; } }
+
+.help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .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; min-height: 4em; }
+
+.pyfile .help_panel { width: 16em; min-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: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; }
+
+#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
+
+#source p { position: relative; white-space: pre; }
+
+#source p * { box-sizing: border-box; }
+
+#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; }
+
+@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
+
+#source p .n a { text-decoration: none; color: #999; }
+
+@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } }
+
+#source p .n a:hover { text-decoration: underline; color: #999; }
+
+@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } }
+
+#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 #fff; }
+
+@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } }
+
+#source p .t:hover { background: #f2f2f2; }
+
+@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } }
+
+#source p .t:hover ~ .r .annotate.long { display: block; }
+
+#source p .t .com { color: #008000; font-style: italic; line-height: 1px; }
+
+@media (prefers-color-scheme: dark) { #source p .t .com { color: #6A9955; } }
+
+#source p .t .key { font-weight: bold; line-height: 1px; }
+
+#source p .t .str { color: #0451A5; }
+
+@media (prefers-color-scheme: dark) { #source p .t .str { color: #9CDCFE; } }
+
+#source p.mis .t { border-left: 0.2em solid #ff0000; }
+
+#source p.mis.show_mis .t { background: #fdd; }
+
+@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } }
+
+#source p.mis.show_mis .t:hover { background: #f2d2d2; }
+
+@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }
+
+#source p.run .t { border-left: 0.2em solid #00dd00; }
+
+#source p.run.show_run .t { background: #dfd; }
+
+@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } }
+
+#source p.run.show_run .t:hover { background: #d2f2d2; }
+
+@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }
+
+#source p.exc .t { border-left: 0.2em solid #808080; }
+
+#source p.exc.show_exc .t { background: #eee; }
+
+@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } }
+
+#source p.exc.show_exc .t:hover { background: #e2e2e2; }
+
+@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }
+
+#source p.par .t { border-left: 0.2em solid #dddd00; }
+
+#source p.par.show_par .t { background: #ffa; }
+
+@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } }
+
+#source p.par.show_par .t:hover { background: #f2f2a2; }
+
+@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }
+
+#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
+
+#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }
+
+@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } }
+
+#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; }
+
+@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } }
+
+@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } }
+
+#source p input:checked ~ .r label.ctx { background: #aef; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }
+
+@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } }
+
+@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } }
+
+#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; }
+
+@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } }
+
+#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #aef; border-radius: .25em; margin-right: 1.75em; }
+
+@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } }
+
+#source p .ctxs span { display: block; text-align: right; }
+
+#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; }
+
+#index table.index { margin-left: -.5em; }
+
+#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; }
+
+@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } }
+
+#index td.name, #index th.name { text-align: left; width: auto; }
+
+#index th { font-style: italic; color: #333; cursor: pointer; }
+
+@media (prefers-color-scheme: dark) { #index th { color: #ddd; } }
+
+#index th:hover { background: #eee; }
+
+@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } }
+
+#index th.headerSortDown, #index th.headerSortUp { white-space: nowrap; background: #eee; }
+
+@media (prefers-color-scheme: dark) { #index th.headerSortDown, #index th.headerSortUp { background: #333; } }
+
+#index th.headerSortDown:after { content: " ↑"; }
+
+#index th.headerSortUp:after { content: " ↓"; }
+
+#index td.name a { text-decoration: none; color: inherit; }
+
+#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: #eee; }
+
+@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } }
+
+#index tr.file:hover td.name { text-decoration: underline; color: inherit; }
+
+#scroll_marker { position: fixed; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; }
+
+@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } }
+
+@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } }
+
+#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; }
+
+@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }
diff --git a/contrib/python/coverage/py3/coverage/htmlfiles/style.scss b/contrib/python/coverage/py3/coverage/htmlfiles/style.scss
new file mode 100644
index 0000000000..158d1fb493
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/htmlfiles/style.scss
@@ -0,0 +1,660 @@
+/* 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
+//
+// OR you can process sass purely in python with `pip install pysass`, then:
+// pysassc --style=compact coverage/htmlfiles/style.scss coverage/htmlfiles/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: 3.5rem;
+
+
+//
+// Declare colors and variables
+//
+
+$font-normal: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+$font-code: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+
+$off-button-lighten: 50%;
+$hover-dark-amt: 95%;
+
+$focus-color: #007acc;
+
+$mis-color: #ff0000;
+$run-color: #00dd00;
+$exc-color: #808080;
+$par-color: #dddd00;
+
+$light-bg: #fff;
+$light-fg: #000;
+$light-gray1: #f8f8f8;
+$light-gray2: #eee;
+$light-gray3: #ccc;
+$light-gray4: #999;
+$light-gray5: #666;
+$light-gray6: #333;
+$light-pln-bg: $light-bg;
+$light-mis-bg: #fdd;
+$light-run-bg: #dfd;
+$light-exc-bg: $light-gray2;
+$light-par-bg: #ffa;
+$light-token-com: #008000;
+$light-token-str: #0451A5;
+$light-context-bg-color: #aef;
+
+$dark-bg: #1e1e1e;
+$dark-fg: #eee;
+$dark-gray1: #222;
+$dark-gray2: #333;
+$dark-gray3: #444;
+$dark-gray4: #777;
+$dark-gray5: #aaa;
+$dark-gray6: #ddd;
+$dark-pln-bg: $dark-bg;
+$dark-mis-bg: #4b1818;
+$dark-run-bg: #373d29;
+$dark-exc-bg: $dark-gray2;
+$dark-par-bg: #650;
+$dark-token-com: #6A9955;
+$dark-token-str: #9CDCFE;
+$dark-context-bg-color: #056;
+
+//
+// Mixins and utilities
+//
+@mixin background-dark($color) {
+ @media (prefers-color-scheme: dark) {
+ background: $color;
+ }
+}
+@mixin color-dark($color) {
+ @media (prefers-color-scheme: dark) {
+ color: $color;
+ }
+}
+@mixin border-color-dark($color) {
+ @media (prefers-color-scheme: dark) {
+ border-color: $color;
+ }
+}
+
+// Add visual outline to navigable elements on focus improve accessibility.
+@mixin focus-border {
+ &:active, &:focus {
+ outline: 2px dashed $focus-color;
+ }
+}
+
+// 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: $font-normal;
+ font-size: 1em;
+ background: $light-bg;
+ color: $light-fg;
+ @include background-dark($dark-bg);
+ @include color-dark($dark-fg);
+}
+
+html>body {
+ font-size: 16px;
+}
+
+a {
+ @include focus-border;
+}
+
+p {
+ font-size: .875em;
+ line-height: 1.4em;
+}
+
+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: $light-gray1;
+ @include background-dark(black);
+ width: 100%;
+ border-bottom: 1px solid $light-gray2;
+ @include border-color-dark($dark-gray2);
+}
+
+.indexfile #footer {
+ margin: 1rem $left-gutter;
+}
+
+.pyfile #footer {
+ margin: 1rem 1rem;
+}
+
+#footer .content {
+ padding: 0;
+ color: $light-gray5;
+ @include color-dark($dark-gray5);
+ font-style: italic;
+}
+
+#index {
+ margin: 1rem 0 0 $left-gutter;
+}
+
+// Header styles
+#header .content {
+ padding: 1rem $left-gutter;
+}
+
+h1 {
+ font-size: 1.25em;
+ display: inline-block;
+}
+
+#filter_container {
+ float: right;
+ margin: 0 2em 0 0;
+
+ input {
+ width: 10em;
+ padding: 0.2em 0.5em;
+ border: 2px solid $light-gray3;
+ background: $light-bg;
+ color: $light-fg;
+ @include border-color-dark($dark-gray3);
+ @include background-dark($dark-bg);
+ @include color-dark($dark-fg);
+ &:focus {
+ border-color: $focus-color;
+ }
+ }
+}
+
+h2.stats {
+ margin-top: .5em;
+ font-size: 1em;
+}
+.stats button {
+ font-family: inherit;
+ font-size: inherit;
+ border: 1px solid;
+ border-radius: .2em;
+ color: inherit;
+ padding: .1em .5em;
+ margin: 1px calc(.1em + 1px);
+ cursor: pointer;
+ border-color: $light-gray3;
+ @include border-color-dark($dark-gray3);
+ @include focus-border;
+
+ @include focus-border;
+
+ &.run {
+ background: mix($light-run-bg, $light-bg, $off-button-lighten);
+ @include background-dark($dark-run-bg);
+ &.show_run {
+ background: $light-run-bg;
+ @include background-dark($dark-run-bg);
+ border: 2px solid $run-color;
+ margin: 0 .1em;
+ }
+ }
+ &.mis {
+ background: mix($light-mis-bg, $light-bg, $off-button-lighten);
+ @include background-dark($dark-mis-bg);
+ &.show_mis {
+ background: $light-mis-bg;
+ @include background-dark($dark-mis-bg);
+ border: 2px solid $mis-color;
+ margin: 0 .1em;
+ }
+ }
+ &.exc {
+ background: mix($light-exc-bg, $light-bg, $off-button-lighten);
+ @include background-dark($dark-exc-bg);
+ &.show_exc {
+ background: $light-exc-bg;
+ @include background-dark($dark-exc-bg);
+ border: 2px solid $exc-color;
+ margin: 0 .1em;
+ }
+ }
+ &.par {
+ background: mix($light-par-bg, $light-bg, $off-button-lighten);
+ @include background-dark($dark-par-bg);
+ &.show_par {
+ background: $light-par-bg;
+ @include background-dark($dark-par-bg);
+ border: 2px solid $par-color;
+ margin: 0 .1em;
+ }
+ }
+}
+
+// Yellow post-it things.
+%popup {
+ display: none;
+ position: absolute;
+ z-index: 999;
+ background: #ffffcc;
+ border: 1px solid #888;
+ border-radius: .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;
+ min-height: 4em;
+ }
+
+ .pyfile & {
+ width: 16em;
+ min-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: $font-code;
+ font-weight: bold;
+ background: #eee;
+ }
+}
+
+// Source file styles
+
+// The slim bar at the left edge of the source lines, colored by coverage.
+$border-indicator-width: .2em;
+
+#source {
+ padding: 1em 0 1em $left-gutter;
+ font-family: $font-code;
+
+ 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: $light-gray4;
+ @include color-dark($dark-gray4);
+
+ a {
+ text-decoration: none;
+ color: $light-gray4;
+ @include color-dark($dark-gray4);
+ &:hover {
+ text-decoration: underline;
+ color: $light-gray4;
+ @include color-dark($dark-gray4);
+ }
+ }
+ }
+
+ &.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 $light-bg;
+ @include border-color-dark($dark-bg);
+
+ &:hover {
+ background: mix($light-pln-bg, $light-fg, $hover-dark-amt);
+ @include background-dark(mix($dark-pln-bg, $dark-fg, $hover-dark-amt));
+
+ & ~ .r .annotate.long {
+ display: block;
+ }
+ }
+
+ // Syntax coloring
+ .com {
+ color: $light-token-com;
+ @include color-dark($dark-token-com);
+ font-style: italic;
+ line-height: 1px;
+ }
+ .key {
+ font-weight: bold;
+ line-height: 1px;
+ }
+ .str {
+ color: $light-token-str;
+ @include color-dark($dark-token-str);
+ }
+ }
+
+ &.mis {
+ .t {
+ border-left: $border-indicator-width solid $mis-color;
+ }
+
+ &.show_mis .t {
+ background: $light-mis-bg;
+ @include background-dark($dark-mis-bg);
+
+ &:hover {
+ background: mix($light-mis-bg, $light-fg, $hover-dark-amt);
+ @include background-dark(mix($dark-mis-bg, $dark-fg, $hover-dark-amt));
+ }
+ }
+ }
+
+ &.run {
+ .t {
+ border-left: $border-indicator-width solid $run-color;
+ }
+
+ &.show_run .t {
+ background: $light-run-bg;
+ @include background-dark($dark-run-bg);
+
+ &:hover {
+ background: mix($light-run-bg, $light-fg, $hover-dark-amt);
+ @include background-dark(mix($dark-run-bg, $dark-fg, $hover-dark-amt));
+ }
+ }
+ }
+
+ &.exc {
+ .t {
+ border-left: $border-indicator-width solid $exc-color;
+ }
+
+ &.show_exc .t {
+ background: $light-exc-bg;
+ @include background-dark($dark-exc-bg);
+
+ &:hover {
+ background: mix($light-exc-bg, $light-fg, $hover-dark-amt);
+ @include background-dark(mix($dark-exc-bg, $dark-fg, $hover-dark-amt));
+ }
+ }
+ }
+
+ &.par {
+ .t {
+ border-left: $border-indicator-width solid $par-color;
+ }
+
+ &.show_par .t {
+ background: $light-par-bg;
+ @include background-dark($dark-par-bg);
+
+ &:hover {
+ background: mix($light-par-bg, $light-fg, $hover-dark-amt);
+ @include background-dark(mix($dark-par-bg, $dark-fg, $hover-dark-amt));
+ }
+ }
+
+ }
+
+ .r {
+ position: absolute;
+ top: 0;
+ right: 2.5em;
+ font-family: $font-normal;
+ }
+
+ .annotate {
+ font-family: $font-normal;
+ color: $light-gray5;
+ @include color-dark($dark-gray6);
+ 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($light-context-bg-color, $light-bg, $off-button-lighten);
+ @include background-dark(mix($dark-context-bg-color, $dark-bg, $off-button-lighten));
+ color: $light-gray5;
+ @include color-dark($dark-gray5);
+ }
+ }
+
+ &:checked ~ .r label.ctx {
+ background: $light-context-bg-color;
+ @include background-dark($dark-context-bg-color);
+ color: $light-gray5;
+ @include color-dark($dark-gray5);
+ 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: $light-gray4;
+ @include color-dark($dark-gray4);
+ 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: $font-normal;
+ white-space: nowrap;
+ background: $light-context-bg-color;
+ @include background-dark($dark-context-bg-color);
+ border-radius: .25em;
+ margin-right: 1.75em;
+ span {
+ display: block;
+ text-align: right;
+ }
+ }
+ }
+}
+
+
+// index styles
+#index {
+ font-family: $font-code;
+ font-size: 0.875em;
+
+ table.index {
+ margin-left: -.5em;
+ }
+ td, th {
+ text-align: right;
+ width: 5em;
+ padding: .25em .5em;
+ border-bottom: 1px solid $light-gray2;
+ @include border-color-dark($dark-gray2);
+ &.name {
+ text-align: left;
+ width: auto;
+ }
+ }
+ th {
+ font-style: italic;
+ color: $light-gray6;
+ @include color-dark($dark-gray6);
+ cursor: pointer;
+ &:hover {
+ background: $light-gray2;
+ @include background-dark($dark-gray2);
+ }
+ &.headerSortDown, &.headerSortUp {
+ white-space: nowrap;
+ background: $light-gray2;
+ @include background-dark($dark-gray2);
+ }
+ &.headerSortDown:after {
+ content: " ↑";
+ }
+ &.headerSortUp:after {
+ content: " ↓";
+ }
+ }
+ td.name a {
+ text-decoration: none;
+ color: inherit;
+ }
+
+ tr.total td,
+ tr.total_dynamic td {
+ font-weight: bold;
+ border-top: 1px solid #ccc;
+ border-bottom: none;
+ }
+ tr.file:hover {
+ background: $light-gray2;
+ @include background-dark($dark-gray2);
+ td.name {
+ text-decoration: underline;
+ color: inherit;
+ }
+ }
+}
+
+// scroll marker styles
+#scroll_marker {
+ position: fixed;
+ right: 0;
+ top: 0;
+ width: 16px;
+ height: 100%;
+ background: $light-bg;
+ border-left: 1px solid $light-gray2;
+ @include background-dark($dark-bg);
+ @include border-color-dark($dark-gray2);
+ will-change: transform; // for faster scrolling of fixed element in Chrome
+
+ .marker {
+ background: $light-gray3;
+ @include background-dark($dark-gray3);
+ position: absolute;
+ min-height: 3px;
+ width: 100%;
+ }
+}
diff --git a/contrib/python/coverage/py3/coverage/inorout.py b/contrib/python/coverage/py3/coverage/inorout.py
new file mode 100644
index 0000000000..cbc80e8fb5
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/inorout.py
@@ -0,0 +1,513 @@
+# 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, debug):
+ self.warn = warn
+ self.debug = debug
+
+ # 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."""
+ self.config = config
+ self.source_pkgs.extend(config.source_pkgs)
+ 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)
+ if getattr(sys, 'is_standalone_binary', False):
+ # don't trace contrib
+ self.omit.append('contrib/python/*')
+ self.omit.append('contrib/libs/protobuf/*')
+ self.omit.append('library/python/pytest/*')
+ self.include = prep_patterns(config.run_include)
+
+ # The directories for files considered "installed with the interpreter".
+ self.pylib_paths = set()
+ if getattr(sys, 'is_standalone_binary', False):
+ self.pylib_paths.add('contrib/tools/python')
+ self.pylib_paths.add('contrib/tools/python3')
+ if not self.pylib_paths and 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.
+ if getattr(sys, 'is_standalone_binary', False):
+ self.cover_paths = ["contrib/python/coverage"]
+ else:
+ 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))
+
+ def debug(msg):
+ if self.debug:
+ self.debug.write(msg)
+
+ # Create the matchers we need for should_trace
+ if self.source or self.source_pkgs:
+ against = []
+ if self.source:
+ self.source_match = TreeMatcher(self.source)
+ against.append("trees {!r}".format(self.source_match))
+ if self.source_pkgs:
+ self.source_pkgs_match = ModuleMatcher(self.source_pkgs)
+ against.append("modules {!r}".format(self.source_pkgs_match))
+ debug("Source matching against " + " and ".join(against))
+ else:
+ if self.cover_paths:
+ self.cover_match = TreeMatcher(self.cover_paths)
+ debug("Coverage code matching: {!r}".format(self.cover_match))
+ if self.pylib_paths:
+ self.pylib_match = TreeMatcher(self.pylib_paths)
+ debug("Python stdlib matching: {!r}".format(self.pylib_match))
+ if self.include:
+ self.include_match = FnmatchMatcher(self.include)
+ debug("Include matching: {!r}".format(self.include_match))
+ if self.omit:
+ self.omit_match = FnmatchMatcher(self.omit)
+ debug("Omit matching: {!r}".format(self.omit_match))
+
+ def should_trace(self, filename, frame=None):
+ """Decide whether to trace execution in `filename`, with a reason.
+
+ 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"
+
+ # XXX maybe we need to support both at the same time?
+ # Don't trace modules imported from environment in standalone mode
+ if getattr(sys, 'is_standalone_binary', False) and filename.startswith("/"):
+ return nope(disp, "skip modules from environment")
+
+ 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:
+ if not self.config.suppress_plugin_errors:
+ raise
+ 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 or self.source_pkgs_match:
+ extra = ""
+ ok = False
+ if self.source_pkgs_match:
+ if self.source_pkgs_match.match(modulename):
+ ok = True
+ if modulename in self.source_pkgs_unmatched:
+ self.source_pkgs_unmatched.remove(modulename)
+ else:
+ extra = "module {!r} ".format(modulename)
+ if not ok and self.source_match:
+ if self.source_match.match(filename):
+ ok = True
+ if not ok:
+ return extra + "falls outside the --source spec"
+ elif self.include_match:
+ if not self.include_match.match(filename):
+ return "falls outside the --include trees"
+ 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/contrib/python/coverage/py3/coverage/jsonreport.py b/contrib/python/coverage/py3/coverage/jsonreport.py
new file mode 100644
index 0000000000..4287bc79a3
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/misc.py b/contrib/python/coverage/py3/coverage/misc.py
new file mode 100644
index 0000000000..034e288eb9
--- /dev/null
+++ b/contrib/python/coverage/py3/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:
+ 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 = {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/contrib/python/coverage/py3/coverage/multiproc.py b/contrib/python/coverage/py3/coverage/multiproc.py
new file mode 100644
index 0000000000..21ed2e2c95
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/multiproc.py
@@ -0,0 +1,117 @@
+# 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"
+
+COVERAGE_CONFIGURATION_ENV = "_COVERAGE_CONFIGURATION_ENV"
+
+
+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):
+ """Wrapper around _bootstrap to start coverage."""
+ try:
+ from coverage import Coverage # avoid circular import
+ import json
+ kwconf = json.loads(os.environ[COVERAGE_CONFIGURATION_ENV])
+ cov = Coverage(**kwconf)
+ 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, coverage_args):
+ """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)
+
+ os.environ[COVERAGE_CONFIGURATION_ENV] = coverage_args
+
+ # 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/contrib/python/coverage/py3/coverage/numbits.py b/contrib/python/coverage/py3/coverage/numbits.py
new file mode 100644
index 0000000000..6ca96fbcf7
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/parser.py b/contrib/python/coverage/py3/coverage/parser.py
new file mode 100644
index 0000000000..258f956039
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/parser.py
@@ -0,0 +1,1276 @@
+# 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 definitions.
+ self.raw_classdefs = set()
+
+ # Function definitions (start, end, name)
+ self._raw_funcdefs = 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
+
+ @property
+ def raw_funcdefs(self):
+ return self._raw_funcdefs
+
+ 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())
+
+ # The first line of modules can lie and say 1 always, even if the first
+ # line of code is later. If so, map 1 to the actual first line of the
+ # module.
+ if env.PYBEHAVIOR.module_firstline_1 and self._multiline:
+ self._multiline[1] = min(self.raw_statements)
+
+ def first_line(self, line):
+ """Return the first line number of the statement including `line`."""
+ if line < 0:
+ 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 {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
+ self._raw_funcdefs = aaa.funcdefs
+
+ 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 smsg, emsg in fragment_pairs:
+ 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 _line_numbers(self):
+ """Yield the line numbers possible in this code object.
+
+ Uses co_lnotab described in Python/compile.c to find the
+ line numbers. Produces a sequence: l0, l1, ...
+ """
+ if hasattr(self.code, "co_lines"):
+ for _, _, line in self.code.co_lines():
+ if line is not None:
+ yield line
+ else:
+ # Adapted from dis.py in the standard library.
+ byte_increments = bytes_to_ints(self.code.co_lnotab[0::2])
+ line_increments = bytes_to_ints(self.code.co_lnotab[1::2])
+
+ last_line_num = None
+ line_num = self.code.co_firstlineno
+ byte_num = 0
+ for byte_incr, line_incr in zip(byte_increments, line_increments):
+ if byte_incr:
+ if line_num != last_line_num:
+ yield line_num
+ last_line_num = line_num
+ byte_num += byte_incr
+ if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80:
+ line_incr -= 0x100
+ line_num += line_incr
+ if line_num != last_line_num:
+ yield line_num
+
+ def _find_statements(self):
+ """Find the statements in `self.code`.
+
+ 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._line_numbers():
+ 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 = {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 = []
+ self.funcdefs = set()
+
+ # $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 env.PYBEHAVIOR.module_firstline_1:
+ return 1
+ elif node.body:
+ return self.line_for_node(node.body[0])
+ else:
+ # Empty modules have no line number, they always start at 1.
+ return 1
+
+ # The node types that just flow to the next node with no complications.
+ OK_TO_DEFAULT = {
+ "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 {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 = {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 {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 = {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):
+ start = to_top = self.line_for_node(node.test)
+ constant_test = self.is_constant_expr(node.test)
+ top_is_body0 = False
+ if constant_test and (env.PY3 or constant_test == "Num"):
+ top_is_body0 = True
+ if env.PYBEHAVIOR.keep_constant_test:
+ top_is_body0 = False
+ if top_is_body0:
+ to_top = self.line_for_node(node.body[0])
+ self.block_stack.append(LoopBlock(start=to_top))
+ from_start = ArcStart(start, cause="the condition on line {lineno} was never true")
+ 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 _process_function_def(self, start, node):
+ self.funcdefs.add((start, node.body[-1].lineno, node.name))
+
+ 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._process_function_def(start, node)
+ 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/contrib/python/coverage/py3/coverage/phystokens.py b/contrib/python/coverage/py3/coverage/phystokens.py
new file mode 100644
index 0000000000..54378b3bc8
--- /dev/null
+++ b/contrib/python/coverage/py3/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 = {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/contrib/python/coverage/py3/coverage/plugin.py b/contrib/python/coverage/py3/coverage/plugin.py
new file mode 100644
index 0000000000..6997b489bb
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/plugin_support.py b/contrib/python/coverage/py3/coverage/plugin_support.py
new file mode 100644
index 0000000000..89c1c7658f
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/python.py b/contrib/python/coverage/py3/coverage/python.py
new file mode 100644
index 0000000000..6ff19c34fe
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/python.py
@@ -0,0 +1,261 @@
+# 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 sys
+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, force_fs=False):
+ """Return the source code, as unicode."""
+ if getattr(sys, 'is_standalone_binary', False) and not force_fs:
+ import __res
+
+ modname = __res.importer.file_source(filename)
+ if modname:
+ source = __res.find(modname)
+ source = source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
+ return source.decode('utf-8')
+ else:
+ # it's fake generated package
+ return u''
+ 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/contrib/python/coverage/py3/coverage/pytracer.py b/contrib/python/coverage/py3/coverage/pytracer.py
new file mode 100644
index 0000000000..7ab4d3ef92
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/pytracer.py
@@ -0,0 +1,274 @@
+# 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)
+
+# When running meta-coverage, this file can try to trace itself, which confuses
+# everything. Don't trace ourselves.
+
+THIS_FILE = __file__.rstrip("co")
+
+
+class PyTracer(object):
+ """Python implementation of the raw data tracer."""
+
+ # 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("{} {}[{}]".format(
+ marker,
+ id(self),
+ len(self.data_stack),
+ ))
+ if 0:
+ f.write(".{:x}.{:x}".format(
+ self.thread.ident,
+ self.threading.currentThread().ident,
+ ))
+ f.write(" {}".format(" ".join(map(str, args))))
+ if 0:
+ f.write(" | ")
+ stack = " / ".join(
+ (fname or "???").rpartition("/")[-1]
+ for _, fname, _, _ in self.data_stack
+ )
+ f.write(stack)
+ f.write("\n")
+
+ def _trace(self, frame, event, arg_unused):
+ """The trace function passed to sys.settrace."""
+
+ if THIS_FILE in frame.f_code.co_filename:
+ return None
+
+ #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
+
+ if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable
+ # The PyTrace.stop() method has been called, possibly by another
+ # thread, let's deactivate ourselves now.
+ if 0:
+ self.log("---\nX", frame.f_code.co_filename, frame.f_lineno)
+ f = frame
+ while f:
+ self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace)
+ f = f.f_back
+ sys.settrace(None)
+ self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = (
+ self.data_stack.pop()
+ )
+ return None
+
+ if self.last_exc_back:
+ 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' and frame.f_code.co_filename != self.cur_file_name:
+ # self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno)
+
+ if event == 'call':
+ # Should we start a new context?
+ if self.should_start_context and self.context is None:
+ context_maybe = self.should_start_context(frame)
+ 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 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/contrib/python/coverage/py3/coverage/report.py b/contrib/python/coverage/py3/coverage/report.py
new file mode 100644
index 0000000000..64678ff95d
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/results.py b/contrib/python/coverage/py3/coverage/results.py
new file mode 100644
index 0000000000..4916864df3
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/results.py
@@ -0,0 +1,343 @@
+# 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]
+ missing = len(missing_arcs[lnum])
+ 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 # pragma: not covered (we never call it this way)
+
+
+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 and ex 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/contrib/python/coverage/py3/coverage/sqldata.py b/contrib/python/coverage/py3/coverage/sqldata.py
new file mode 100644
index 0000000000..a150fdfd0f
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/sqldata.py
@@ -0,0 +1,1123 @@
+# 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`,
+ or :meth:`touch_files` for a list of such files.
+
+ 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 line measurements to existing branch data")
+ if arcs and self._has_lines:
+ raise CoverageException("Can't add branch measurements to existing line data")
+ if not self._has_arcs and not self._has_lines:
+ self._has_lines = lines
+ self._has_arcs = arcs
+ 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.
+ """
+ self.touch_files([filename], plugin_name)
+
+ def touch_files(self, filenames, plugin_name=""):
+ """Ensure that `filenames` appear in the data, empty if needed.
+
+ `plugin_name` is the name of the plugin responsible for these files. It is used
+ to associate the right filereporter, etc.
+ """
+ if self._debug.should('dataop'):
+ self._debug.write("Touching %r" % (filenames,))
+ self._start_using()
+ with self._connect(): # Use this to get one transaction.
+ if not self._has_arcs and not self._has_lines:
+ raise CoverageException("Can't touch files in an empty CoverageData")
+
+ for filename in filenames:
+ 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 = {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({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:
+ try:
+ return self.con.execute(sql, parameters)
+ except Exception:
+ # In some cases, an error might happen that isn't really an
+ # error. Try again immediately.
+ # https://github.com/nedbat/coveragepy/issues/1010
+ 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/contrib/python/coverage/py3/coverage/summary.py b/contrib/python/coverage/py3/coverage/summary.py
new file mode 100644
index 0000000000..65f8047006
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/summary.py
@@ -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
+
+"""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 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:
+ 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))
+
+ # Sort the lines and write them out.
+ if getattr(self.config, 'sort', None):
+ sort_option = self.config.sort.lower()
+ reverse = False
+ if sort_option[0] == '-':
+ reverse = True
+ sort_option = sort_option[1:]
+ elif sort_option[0] == '+':
+ sort_option = sort_option[1:]
+
+ position = column_order.get(sort_option)
+ if position is None:
+ raise CoverageException("Invalid sorting option: {!r}".format(self.config.sort))
+ lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse)
+
+ for line in lines:
+ self.writeout(line[0])
+
+ # Write a TOTAL line if we had at least one file.
+ if self.total.n_files > 0:
+ self.writeout(rule)
+ args = ("TOTAL", self.total.n_statements, self.total.n_missing)
+ if self.branches:
+ 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/contrib/python/coverage/py3/coverage/templite.py b/contrib/python/coverage/py3/coverage/templite.py
new file mode 100644
index 0000000000..7d4024e0af
--- /dev/null
+++ b/contrib/python/coverage/py3/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/contrib/python/coverage/py3/coverage/tomlconfig.py b/contrib/python/coverage/py3/coverage/tomlconfig.py
new file mode 100644
index 0000000000..3ad581571c
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/tomlconfig.py
@@ -0,0 +1,168 @@
+# 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
+
+# TOML support is an install-time extra option.
+try:
+ import toml
+except ImportError: # pragma: not covered
+ toml = None
+
+
+class TomlDecodeError(Exception):
+ """An exception class that exists even when toml isn't installed."""
+ 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):
+ # 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/contrib/python/coverage/py3/coverage/version.py b/contrib/python/coverage/py3/coverage/version.py
new file mode 100644
index 0000000000..d141a11da3
--- /dev/null
+++ b/contrib/python/coverage/py3/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, 5, 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/contrib/python/coverage/py3/coverage/xmlreport.py b/contrib/python/coverage/py3/coverage/xmlreport.py
new file mode 100644
index 0000000000..6d012ee692
--- /dev/null
+++ b/contrib/python/coverage/py3/coverage/xmlreport.py
@@ -0,0 +1,234 @@
+# 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."""
+
+ if self.config.skip_empty:
+ if analysis.numbers.n_statements == 0:
+ return
+
+ # Create the 'lines' and 'package' XML elements, which
+ # are populated later. Note that a package == a directory.
+ filename = fr.filename.replace("\\", "/")
+ for source_path in self.source_paths:
+ source_path = files.canonical_filename(source_path)
+ if filename.startswith(source_path.replace("\\", "/") + "/"):
+ rel_name = filename[len(source_path)+1:]
+ break
+ else:
+ rel_name = fr.relative_filename()
+ self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/"))
+
+ dirname = os.path.dirname(rel_name) or u"."
+ dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth])
+ package_name = dirname.replace("/", ".")
+
+ 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/contrib/python/coverage/py3/ya.make b/contrib/python/coverage/py3/ya.make
new file mode 100644
index 0000000000..76625b48eb
--- /dev/null
+++ b/contrib/python/coverage/py3/ya.make
@@ -0,0 +1,98 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(5.5)
+
+LICENSE(Apache-2.0)
+
+PEERDIR(
+ contrib/python/coverage/plugins
+ library/python/resource
+)
+
+NO_COMPILER_WARNINGS()
+
+NO_LINT()
+
+NO_CHECK_IMPORTS(
+ coverage.fullcoverage.encodings
+)
+
+SRCS(
+ coverage/ctracer/datastack.c
+ coverage/ctracer/filedisp.c
+ coverage/ctracer/module.c
+ coverage/ctracer/tracer.c
+)
+
+PY_REGISTER(
+ coverage.tracer
+)
+
+PY_SRCS(
+ TOP_LEVEL
+ coverage/__init__.py
+ coverage/__main__.py
+ coverage/annotate.py
+ coverage/backward.py
+ coverage/bytecode.py
+ coverage/cmdline.py
+ coverage/collector.py
+ coverage/config.py
+ coverage/context.py
+ coverage/control.py
+ coverage/data.py
+ coverage/debug.py
+ coverage/disposition.py
+ coverage/env.py
+ coverage/execfile.py
+ coverage/files.py
+ coverage/fullcoverage/encodings.py
+ coverage/html.py
+ coverage/inorout.py
+ coverage/jsonreport.py
+ coverage/misc.py
+ coverage/multiproc.py
+ coverage/numbits.py
+ coverage/parser.py
+ coverage/phystokens.py
+ coverage/plugin.py
+ coverage/plugin_support.py
+ coverage/python.py
+ coverage/pytracer.py
+ coverage/report.py
+ coverage/results.py
+ coverage/sqldata.py
+ coverage/summary.py
+ coverage/templite.py
+ coverage/tomlconfig.py
+ coverage/version.py
+ coverage/xmlreport.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/coverage/py3/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+ coverage/htmlfiles/coverage_html.js
+ coverage/htmlfiles/favicon_32.png
+ coverage/htmlfiles/index.html
+ coverage/htmlfiles/jquery.ba-throttle-debounce.min.js
+ coverage/htmlfiles/jquery.hotkeys.js
+ coverage/htmlfiles/jquery.isonscreen.js
+ coverage/htmlfiles/jquery.min.js
+ coverage/htmlfiles/jquery.tablesorter.min.js
+ coverage/htmlfiles/keybd_closed.png
+ coverage/htmlfiles/keybd_open.png
+ coverage/htmlfiles/pyfile.html
+ coverage/htmlfiles/style.css
+ coverage/htmlfiles/style.scss
+)
+
+END()
+
+RECURSE(
+ bin
+)