Who tests what is here: Coverage.py 5.0a3

Sunday 7 October 2018

A long-awaited feature of coverage.py is now available in a rough form: Who Tests What annotates coverage data with the name of the test function that ran the code.

To try it out:

  • Install coverage.py v5.0a3.
  • Add this line literally to the [run] section of your .coveragerc file:
  • [run]
    dynamic_context = test_function
  • Run your tests.
  • The .coverage file is now a SQLite database. There is no change to reporting yet, so you will need to do your own querying of the SQLite database to get information out. See below for a description of the database schema.

The database can be accessed in any SQLite-compatible way you like. Note that the schema is not (yet) part of the public API. That is, it may not be guaranteed to stay the same. This is one of the things yet to be decided. For now though, the database has these tables:

  • file: maps full file paths to file_ids: id, path
  • context: maps contexts (test function names) to contexts_ids: id, context
  • line: the line execution data: file_id, context_id, lineno
  • arc: similar to line, but for branch coverage: file_id, context_id, fromno, tono

It’s not the most convenient, but the information is all there. If you used branch coverage, then the important data is in the “arc” table, and “line” is empty. If you didn’t use branch coverage, then “line” has data and “arc” is empty. For example, using the sqlite3 command-line tool, here’s a query to see which tests ran a particular line:

sqlite> select
   ...> distinct context.context from arc, file, context
   ...> where arc.file_id = file.id
   ...> and arc.context_id = context.id
   ...> and file.path like '%/xmlreport.py'
   ...> and arc.tono = 122;
context
------------------------------------------------------------
XmlPackageStructureTest.test_package_names
OmitIncludeTestsMixin.test_omit
OmitIncludeTestsMixin.test_include
OmitIncludeTestsMixin.test_omit_2
XmlReportTest.test_filename_format_showing_everything
XmlReportTest.test_no_source
OmitIncludeTestsMixin.test_include_as_string
OmitIncludeTestsMixin.test_omit_and_include
XmlReportTest.test_empty_file_is_100_not_0
OmitIncludeTestsMixin.test_omit_as_string
XmlReportTest.test_nonascii_directory
OmitIncludeTestsMixin.test_nothing_specified
XmlReportTest.test_curdir_source
XmlReportTest.test_deep_source
XmlPackageStructureTest.test_package_depth
XmlPackageStructureTest.test_source_prefix
XmlGoldTest.test_a_xml_2
XmlGoldTest.test_a_xml_1
XmlReportTest.test_filename_format_including_module
XmlReportTest.test_reporting_on_nothing
XmlReportTest.test_filename_format_including_filename
ReportingReturnValueTest.test_xml
OmitIncludeTestsMixin.test_include_2

BTW, there are also “static contexts” if you are interested in keeping coverage data from different test runs separate: see Measurement Contexts in the docs for details.

Some things to note and think about:

  • The test function name recorded includes the test class if we can figure it out. Sometimes this isn’t possible. Would it be better to record the filename and line number?
  • Is test_function too fine-grained for some people? Maybe chunking to the test class or even the test file would be enough?
  • Better would be to have test runner plugins that could tell us the test identifier. Anyone want to help with that?
  • What other kinds of dynamic contexts might be useful?
  • What would be good ways to report on this data? How are you navigating the data to get useful information from it?
  • How is the performance?
  • We could have a “coverage extract” command that would be like the opposite of “coverage combine”: it could pull out a subset of the data so a readable report could be made from it.

Please try this out, and let me know how it goes. Thanks.

Comments

[gravatar]
Kurt McKee 4:37 PM on 7 Oct 2018

Hi Ned! Thanks for all of your work on coverage -- it's an extremely helpful tool!

I wanted to mention that "test_function" seems like an appropriately fine-grained depth. In my line of work I deal with hardware platforms that have minor behavioral differences between firmware versions. Consequently, there are many places where a conditional is only true for a specific firmware version, so when I write unit tests, each one typically triggers one specific corner case.

Being able to confirm which individual test function is triggering a specific path seems really helpful. In comparison, chunking to the test class or test file would not be helpful in my specific use case.

I don't have other feedback beyond describing a little bit of my use case. Thank you again for all of your work on coverage!

[gravatar]
Florian Kromer 1:33 PM on 8 Oct 2018

Hi Ned.

Great to hear that "Who tests what?" is ready! Thanks a lot for all the effort put into this essential tool!

I'm pretty sure it will ease implementation of python-tia, a generic Test Impact Analysis (TIA) preprocessor for test tools and static analyzers.

> The test function name recorded includes the test class if we can figure it out. Sometimes this isn’t possible. Would it be better to record the filename and line number?

In case this is an either/or decision I'd prefer test_class.test_function. Would it be too costly to add filename and line number?

> Is test_function too fine-grained for some people? Maybe chunking to the test class or even the test file would be enough?

No. I need the test_function. Having the information about the full path (test_file.test_class.test_function) could be great for further processing.

> Better would be to have test runnner plugins that could tell us the test identifier. Anyone want to help with that?

I'd prefer coverage.py beeing independent on test runners. However this is not possible if we want to get other dynamic contexts. Helping out... depends on spare time and relevance for python-tia.

> What other kinds of dynamic contexts might be useful?

W.r.t. improvement of the granularity of test impact analysis I thought about how to get information about the mapping of tests to their run-time dependencies like pytest fixtures a test depends on, test utility functions a test depends on, etc. .

> What would be good ways to report on this data? How are you navigating the data to get useful information from it?

Reporting is not of primary interest for me. Accessing the stored data from a SQLite file (data location and internal representation) is just fine for me. With python-tia I'd like to stick to a functional programming approach with corresponding data structures. Means I'd read the SQLite data, output an iterable sequence of e.g. namedtuples and work with this.

> How is the performance?

Let's see :)

> We could have a “coverage extract” command that would be like the opposite of “coverage combine”: it could pull out a subset of the data so a readable report could be made from it.

Having a command line command is a good idea. For python-tia I'd prefer to have an API to access the coverage.py SQLite data.

[gravatar]
Ned Batchelder 12:21 AM on 9 Oct 2018

@Florian One idea for the dynamic contexts is to be able to ask for these components: test_filename, test_lineno, test_class, test_function. You could specify whatever combination you wanted to get the granularity and information you need.

[gravatar]
Florian Kromer 10:19 PM on 11 Oct 2018

@Ned That sounds great!

[gravatar]
Michał Bultrowicz 2:39 PM on 13 Oct 2018

It might be kind of late to tell that, but I had a hack that was tied to the format of .coverage file (I know, I shouldn't have done that) that allowed me to merge partial coverage information from differenc instances of the same codebase running in different file hierarchies.

I had some tests that were your standard unit tests gathering some coverage data with the parallel option set. Then I had functional tests that excercised the app running in a Docker container with enabled coverage measurement. After the tests I would copy the partial coverage information down from the container. The problem was that the code paths in the local tests were different from those in the container, so I had to replace them in .coverage file from the container before running "coverage combine".

Is there a nice way to work around this issue that's built into coverage that I don't know about? If not, then should I just be doing the same trick, but with altering / merging SQLite databases?

[gravatar]
Ned Batchelder 9:43 AM on 15 Oct 2018

Michał and I worked out over email that the [paths] section of the config file does what he needs. :)

[gravatar]
Pankaj 9:36 AM on 24 Oct 2018

Ned, I have a requirement which I think might be useful to cater to. Our test cases are independently spawned processess (hundreds of them), and identified by filename and a test name within the file, and I want the context to be something like "path/to/test_case:test_name", so this is a static context for a run.
But coverage is run using the https://coverage.readthedocs.io/en/v4.5.x/subprocess.html#subprocess COVERAGE_PROCESS_START env var, so I cannot set the static context via cli. Would it be possible for you to add option for setting the static context via an environment variable and/or an argument to coverage.process_startup().
I realize this is not a place to file feature request so I can create a github issue if you say.

[gravatar]
Ned Batchelder 10:01 AM on 24 Oct 2018

@Pankaj, thanks for the details. You can already do what you want, because the .coveragerc file will expand environment variables. If you use "context=${CONTEXT}" in your rc file, then you can set the CONTEXT environment variable, and you are good to go.

[gravatar]
Pankaj 10:39 AM on 24 Oct 2018

Thanks a lot Ned, this is super useful. I've wanted environment variable expansion in the coveragerc file since long but never realized it is already implemented.

[gravatar]
Tony Flury 8:51 AM on 30 Oct 2018

I use the html reporting all of the time, and it would be useful per line to have a clickable/icon per line to drill down to the test cases that executed that line - it would be useful as well if that html report was grouped by context. So classes and then functions in class. How much of this is possible I don't know - it would mean a lot of generated files for the html report (a main report per source file, and a drill down report per file).
A comannd line option on the coverage html would be good to turn off this drill down (off being the default).

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.