Skip to content

Instantly share code, notes, and snippets.

@dhermes
Last active September 24, 2019 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhermes/673714859929877d08f45a58ab9c59ee to your computer and use it in GitHub Desktop.
Save dhermes/673714859929877d08f45a58ab9c59ee to your computer and use it in GitHub Desktop.
Testing out Coverage.py In-flight Save
[run]
branch = True
concurrency = multiprocessing
data_file = coverage-report.bin
parallel = True
[html]
directory = htmlcov
venv/
.coverage
coverage-report.bin*
htmlcov/

Using coverage.py Without Stopping

Testing out Coverage.py In-flight Save

We'll run four servers which we interact with via curl:

$ make run
main(): <coverage.control.Coverage object at 0x10d3ce3c8>
p() running in thread Thread-1: <coverage.control.Coverage object at 0x10d3ce3c8>
q() running in thread Thread-2: <coverage.control.Coverage object at 0x10d3ce3c8>
r() running in process Process-1: <coverage.control.Coverage object at 0x10d4e94e0>
s() running in process Process-2: <coverage.control.Coverage object at 0x10d7257b8>
...

Get Report While Running

In another shell, we can invoke the routes used to save without shutting down any of the servers:

$ ./invoke.sh
+ curl localhost:8000/noop
+ sleep 0.05
+ curl localhost:8001/save
+ sleep 0.05
+ curl localhost:8000/save
+ sleep 0.05
+ curl localhost:8002/save
+ sleep 0.05
+ curl localhost:8002/save
+ sleep 0.05
+ curl localhost:8003/save
+ sleep 0.05
+ curl localhost:8003/save
+ sleep 0.05

which results in

$ make run
...
s() running in process Process-2: <coverage.control.Coverage object at 0x10d7257b8>
8000 | p() | /noop
8001 | q() | /save
Saving coverage in q(): <coverage.control.Coverage object at 0x10d3ce3c8>
8000 | p() | /save
Saving coverage in p(): <coverage.control.Coverage object at 0x10d3ce3c8>
8002 | r() | /save
Saving coverage in r(): <coverage.control.Coverage object at 0x10d4e94e0>
8002 | r() | /save
Saving coverage in r(): <coverage.control.Coverage object at 0x10d4e94e0>
8003 | s() | /save
Saving coverage in s(): <coverage.control.Coverage object at 0x10d7257b8>
8003 | s() | /save
Saving coverage in s(): <coverage.control.Coverage object at 0x10d7257b8>
...

The /save calls in the multiprocessing.Process workers end up producing 2 extra files:

$ ls -1 coverage-report.bin*
coverage-report.bin.${hostname}.4837.526273
coverage-report.bin.${hostname}.4840.757112
coverage-report.bin.${hostname}.4841.949654

and the report that reflects the fact that we've hit the lines for the server other than shutdown and process cleanup:

$ make report
Name      Stmts   Miss Branch BrPart  Cover   Missing
-----------------------------------------------------
main.py      70      9      6      2    86%   19-20, 22->23, 23, 89-95, 98->exit

Shutodwn

If we shutdown the servers

$ ./shutdown.sh
+ curl localhost:8000/shutdown
+ curl localhost:8001/shutdown
+ curl localhost:8002/shutdown
+ curl localhost:8003/shutdown

the process will exit with

$ make run
...
Saving coverage in s(): <coverage.control.Coverage object at 0x10d7257b8>
8000 | p() | /shutdown
8001 | q() | /shutdown
8002 | r() | /shutdown
8003 | s() | /shutdown
Coverage.py warning: No data was collected. (no-data-collected)

and we see 3 newly saved reports next to the previously generated (via coverage combine) report:

$ ls -1 coverage-report.bin*
coverage-report.bin
coverage-report.bin.${hostname}.4837.526273
coverage-report.bin.${hostname}.4840.757112
coverage-report.bin.${hostname}.4841.949654

However, two of the reports have no data

$ du -h coverage-report.bin.*
  0B    coverage-report.bin.${hostname}.4840.757112
  0B    coverage-report.bin.${hostname}.4841.949654
#!/bin/sh
set -e -x
curl localhost:8000/noop; sleep 0.05
curl localhost:8001/save; sleep 0.05
curl localhost:8000/save; sleep 0.05
curl localhost:8002/save; sleep 0.05
curl localhost:8002/save; sleep 0.05
curl localhost:8003/save; sleep 0.05
curl localhost:8003/save; sleep 0.05
import http.server
import multiprocessing
import threading
import coverage
class ExitingHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
server = self.server
self.wfile.write(b"HTTP/1.1 200 OK\r\n")
self.send_response(200, "OK")
if self.path == "/save":
cov = coverage.Coverage.current()
# NOTE: If ``cov is None``, we have made a mistake somewhere.
print(f"Saving coverage in {server.name}(): {cov}")
cov.stop()
cov.save()
cov.start()
if self.path == "/shutdown":
threading.Timer(0.125, server.shutdown).start()
def log_request(self, code="-", size="-"):
server = self.server
print(f"{server.server_port} | {server.name}() | {self.path}")
def p():
current_thread = threading.current_thread()
cov = coverage.Coverage.current()
print(f"p() running in thread {current_thread.name}: {cov}")
httpd = http.server.HTTPServer(("", 8000), ExitingHandler)
httpd.name = "p"
httpd.serve_forever()
def q():
current_thread = threading.current_thread()
cov = coverage.Coverage.current()
print(f"q() running in thread {current_thread.name}: {cov}")
httpd = http.server.HTTPServer(("", 8001), ExitingHandler)
httpd.name = "q"
httpd.serve_forever()
def r():
cov = coverage.Coverage.current()
current_process = multiprocessing.current_process()
print(f"r() running in process {current_process.name}: {cov}")
httpd = http.server.HTTPServer(("", 8002), ExitingHandler)
httpd.name = "r"
httpd.serve_forever()
def s():
cov = coverage.Coverage.current()
current_process = multiprocessing.current_process()
print(f"s() running in process {current_process.name}: {cov}")
httpd = http.server.HTTPServer(("", 8003), ExitingHandler)
httpd.name = "s"
httpd.serve_forever()
def in_main():
cov = coverage.Coverage.current()
print(f"main(): {cov}")
def main():
in_main()
process1 = multiprocessing.Process(target=r)
thread1 = threading.Thread(target=p)
thread2 = threading.Thread(target=q)
process2 = multiprocessing.Process(target=s)
process1.start()
thread1.start()
thread2.start()
process2.start()
process1.join()
thread1.join()
thread2.join()
process2.join()
cov = coverage.Coverage.current()
cov.stop()
cov.save()
if __name__ == "__main__":
main()
.PHONY: help
help:
@echo 'Makefile for hack with `coverage.py`'
@echo ''
@echo 'Usage:'
@echo ' make requirements.txt Generate `requirements.txt`.'
@echo ' make venv Create virtual environment.'
@echo ' make run Run `main.py` and measure coverage'
@echo ' make report Print coverage report for `main.py` run'
@echo ''
requirements.txt: requirements.txt.in
@pip-compile --generate-hashes --output-file=requirements.txt requirements.txt.in
venv: requirements.txt
@python -m virtualenv --python=python3.7 venv
@venv/bin/python -m pip install --requirement requirements.txt
.PHONY: run
run: main.py venv
@venv/bin/coverage run main.py
.PHONY: report
report:
@venv/bin/coverage combine --append
@venv/bin/coverage report --show-missing
@venv/bin/coverage html
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --generate-hashes --output-file=requirements.txt requirements.txt.in
#
coverage==5.0a7 \
--hash=sha256:094378c3a35594335a840ea04d473c019e6d4fe10e343cd0d7fb5e87f8b7e926 \
--hash=sha256:10216222f3e4139910b6230d0ca0fe9d10ee98837eb83d29525722d628729d20 \
--hash=sha256:147478e21cba12c63b3454df5a2fb77b44df630428cffa3a36a6813e38157eab \
--hash=sha256:230ce08965190c0f69196be34a07a795981b2b02b21419c2e1918a882b3eeab0 \
--hash=sha256:2469621d680a4c71cdbd3ea4dbed9d199bba93f21d2be1c107ded907b2db41a8 \
--hash=sha256:26526174d11fb2163832628d943edd452e07528b0ecc0c83c88256a59a32287c \
--hash=sha256:2690bf0835f34ef3451860b02471e9560e4b3caf7413abeaa7544af72eb6d9ed \
--hash=sha256:2b6f2d9a60413e75651cebe33c3f2f66d61209db44e8b9cf6d8d66fb0cb01fda \
--hash=sha256:3ce91c6b92160ecefedf95a8c61fbf4fb36b0addef1a40c654acf1ad390653d0 \
--hash=sha256:43d16d7e9e9eaace3d9f1828b617b1be248f90d031a4b2dc1b6e1c88f1602dcf \
--hash=sha256:52b6455da5f547cad72fd5cfc57a16678573fda6c695d257b5c266a44dbbd172 \
--hash=sha256:533f3036c8f58e6381fcca3306fe988740638c62c7fc86b7fae9c74b85ac3cdc \
--hash=sha256:62d2abe5c733394058cb381d088bcab64a18da3ce9dc9a8ef2a18e122cbe47f1 \
--hash=sha256:72c34f99164679e44a5cbf19bf1a13be4e715c680816302b6ceca49b979fde91 \
--hash=sha256:81fc07feed4e40a7c0bdd266efa65e5afc83b5e0f1063007acc6759a957322a1 \
--hash=sha256:82093e673182c761ce54dfab17f026a06be3c011fee9b653855b9a2649f20232 \
--hash=sha256:87947fef728f72860407c446fd9b4a0f98e39e91ad7ae80803c02a85738e63ef \
--hash=sha256:8b18c5a5a6b35b6311d2c356782ce3c7bacf6d987d9dc479178577391bf1c7dd \
--hash=sha256:90e1850e993aa6b81bafaf672c8e508eaa17fbb5eb23aba93f7f4df822f3bd29 \
--hash=sha256:99f71e365bcb03a8debe1a75061329c9e45379f244a229442319d64c53c4e844 \
--hash=sha256:9b2c559104a90bf0043d6ef262ca205326d1fe6ec572dcf59e34be9289432793 \
--hash=sha256:ad22b073d92ea65b063e612154c72d6367dec3dd47ed33c02e3ab339eabe7bf3 \
--hash=sha256:bc3648da235fee2113a8cb80154d9fff4e2689d2d4a11ad35c1ecae23454b539 \
--hash=sha256:d0e2478bde68c5d853bcd306b5aae8fbe80417e87957a21fa6ee71edb90639f2 \
--hash=sha256:d3e6912d2370925222d2bfb3bd2ba02e9698b8da89cf7192ddf80cbb9f2455ee \
--hash=sha256:d4fa98e3e15863568ea89eaec5e0866ca763980bdc56098dd9316865c111a28e \
--hash=sha256:ee924a23457b373241ff39d21570360afd8ccb58520eb1e8e18eb00827b73e2d
#!/bin/sh
set -e -x
curl localhost:8000/shutdown
curl localhost:8001/shutdown
curl localhost:8002/shutdown
curl localhost:8003/shutdown
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment