tox -e post
As you can see ``post`` environment closes the build. Unless the build was successful it won't run
and no coverage will be submitted or build artifacts become available. The build artifacts, what
also needs to be listed in Drone.io, are several quality assurance reports that can be helpful for
further improving the codebase::
coverage_report.txt
coverage_report.html.tgz
maintenance_index.txt
code_complexity.txt
For Codecov.io integration to work, environment variable ``CODECOV_TOKEN`` should be assigned.
Tagged Tox configuration
------------------------
Final Tox configuration looks like the following. It emerged through series of rewrites that were
led by tradeoffs between test duration, combined coverage report and need to test dependencies.
I came to the last design after I realised importance of support of pyOpenSSL and thus the need to
test it (see about SSL issues below). Then test sampling facility was easy to integrate to it.
.. sourcecode:: ini
[tox]
minversion = 1.8
envlist = pre,docs,py{26-co,27-qa,33-nt,34-qa}{,-ssl,-ossl}
# run tests from install dir, not checkout dir
[testenv]
changedir = {envsitepackagesdir}/cherrypy
setenv =
qa: COVERAGE_FILE = {toxinidir}/.coverage.{envname}
ssl: CHERRYPY_TEST_SSL_MODULE = builtin
ossl: CHERRYPY_TEST_SSL_MODULE = pyopenssl
sa: CHERRYPY_TEST_XML_REPORT_DIR = {toxinidir}/test-xml-data
deps =
routes
py26: unittest2
py{26,27}: python-memcached
py{33,34}: python3-memcached
qa: coverage
ossl: pyopenssl
sa: unittest-xml-reporting
commands =
python --version
nt: python -m unittest {posargs: discover -v cherrypy.test}
co: unit2 {posargs:discover -v cherrypy.test}
qa: coverage run --branch --source="." --omit="t*/*" \
qa: --module unittest {posargs: discover -v cherrypy.test}
sa: python test/xmltestreport.py
[testenv:docs]
basepython = python2.7
changedir = docs
commands = sphinx-build -q -E -n -b html . build
deps =
sphinx < 1.3
sphinx_rtd_theme
[testenv:pre]
changedir = {toxinidir}
deps = coverage
commands = coverage erase
# must be run separately after main envlist, because of detox
[testenv:post]
changedir = {toxinidir}
deps =
coverage
codecov
radon
commands =
bash -c 'echo -e "[paths]\nsource = \n cherrypy" > .coveragerc'
bash -c 'echo " .tox/*/lib/*/site-packages/cherrypy" >> .coveragerc'
coverage combine
bash -c 'coverage report > coverage_report.txt'
coverage html
tar -czf coverage_report.html.tgz htmlcov
- codecov
bash -c 'radon mi -s -e "cherrypy/t*/*" cherrypy > maintenance_index.txt'
bash -c 'radon cc -s -e "cherrypy/t*/*" cherrypy > code_complexity.txt'
whitelist_externals =
/bin/tar
/bin/bash
# parse and collect XML files of XML test runner
[testenv:report]
deps =
changedir = {toxinidir}
commands = python -m cherrypy.test.xmltestreport --parse
This design heavily relies on great multi-dimensional configuration that appeared in Tox 1.8 [11]_.
Given an environment, say ``py27-nt-ossl``, Tox treats it like a set of tags,
``{'py27', 'nt', 'ossl'}``, where each entry is matched individually. This way any environment,
that makes sense though, with desired qualities can be constructed even if it doesn't listed in
``envlist``. Here's a table that explains the purpose of the tags.
==== ================================================
tag meaning
==== ================================================
nt normal test run via ``unittest``
co compatibility run via ``unittest2``
qa quality assurance run via ``coverage``
sa test sampling run via ``xmlrunner``
ssl run using Python bulitin ``ssl`` module
ossl run using PyOpenSSL
==== ================================================
Note ``{posargs: discover -v cherrypy.test}`` part. It makes possible to run a subset of the
test suite in desired environments, e.g.:
.. sourcecode:: bash
tox -e "py{26-co-ssl,27-nt,33-qa,34-nt-ssl}" -- -v cherrypy.test.test_http \
cherrypy.test.test_conn.TestLimitedRequestQueue \
cherrypy.test.test_caching.TestCache.test_antistampede
Tackling entropy of threading and in-process server juggling
------------------------------------------------------------
Even though there was a progress on making test suite more predictable, and actually runnable,
it is still far from desired, and I was scratching my head trying to figure out how to gather
test stats from all the environments. Solution was in the lighted place, right where I was
looking for it. There's a package *unittest-xml-reporting*, whose name is pretty descriptive.
It's a ``unittest`` runner that writes XML files likes
``TEST-test.test_auth_basic.TestBasicAuth-20150410005927.xml`` with easy-to-guess content.
Then it was just a need to write some glue code for overnight runs and implement parsing and
aggregation.
I've implemented and incorporated module named ``xmltestreport`` [12]_ into the ``test``
package. ``sa`` Tox tag corresponds to it. Running the following in terminal at evening,
at morning can give you the following table (except N/A evironments, see below).
.. sourcecode:: bash
for i in {1..64}; do tox -e "py{26,27,33,34}-sa{,-ssl,-ossl}"; done
tox -e report
+-------------------------------------------------------------+----+----+----+
| **py26-sa** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| OK |
+-------------------------------------------------------------+----+----+----+
| **py26-sa-ossl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestLimitedRequestQueue.test_queue_full | 27 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_request_obj.TestRequestObject.testParamErrors | 1 | 0 | 26 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 27 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| **py26-sa-ssl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_caching.TestCache.test_antistampede | 0 | 33 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_config_server.TestServerConfig.testMaxRequestSize | 0 | 1 | 32 |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestLimitedRequestQueue.test_queue_full | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| **py27-sa** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| OK |
+-------------------------------------------------------------+----+----+----+
| **py27-sa-ossl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestLimitedRequestQueue.test_queue_full | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_session.TestSession.testFileConcurrency | 1 | 0 | 32 |
+-------------------------------------------------------------+----+----+----+
| test_states.TestWait.test_wait_for_occupied_port_INADDR_ANY | 1 | 0 | 32 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_tools.TestTool.testCombinedTools | 2 | 0 | 31 |
+-------------------------------------------------------------+----+----+----+
| **py27-sa-ssl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_caching.TestCache.test_antistampede | 0 | 33 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_config_server.TestServerConfig.testMaxRequestSize | 0 | 4 | 29 |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestLimitedRequestQueue.test_queue_full | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_tools.TestTool.testCombinedTools | 2 | 0 | 31 |
+-------------------------------------------------------------+----+----+----+
| **py33-sa** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestPipeline.test_HTTP11_pipelining | 0 | 16 | 17 |
+-------------------------------------------------------------+----+----+----+
| **py33-sa-ossl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| N/A |
+-------------------------------------------------------------+----+----+----+
| **py33-sa-ssl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_caching.TestCache.test_antistampede | 0 | 33 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 33 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_tools.TestTool.testCombinedTools | 3 | 0 | 30 |
+-------------------------------------------------------------+----+----+----+
| **py34-sa** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestLimitedRequestQueue.test_queue_full | 0 | 1 | 32 |
+-------------------------------------------------------------+----+----+----+
| test_conn.TestPipeline.test_HTTP11_pipelining | 0 | 14 | 19 |
+-------------------------------------------------------------+----+----+----+
| **py34-sa-ossl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| N/A |
+-------------------------------------------------------------+----+----+----+
| **py34-sa-ssl** | F | E | S |
+-------------------------------------------------------------+----+----+----+
| test_caching.TestCache.test_antistampede | 0 | 32 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_config_server.TestServerConfig.testMaxRequestSize | 0 | 1 | 31 |
+-------------------------------------------------------------+----+----+----+
| test_static.TestStatic.test_file_stream | 32 | 0 | 0 |
+-------------------------------------------------------------+----+----+----+
| test_tools.TestTool.testCombinedTools | 3 | 0 | 29 |
+-------------------------------------------------------------+----+----+----+
Actually, as you can see, it only had time for half of the iterations, and at the morning I
interrupted it. Sylvain, you see, I mentioned those three tests in the group correctly.
But there're other candidates and this new arisen fragility of SSL sockets, if I understand it
correctly. I mean all the tests with one fail or error. For example,
``test_session.TestSession.testFileConcurrency`` which is fine lock-wise but because it stresses
networking layer with several dozen client threads, there're two socket errors::
Exception in thread Thread-1098: