Measuring sub-processes

Complex test suites may spawn sub-processes to run tests, either to run them in parallel, or because sub-process behavior is an important part of the system under test. Measuring coverage in those sub-processes can be tricky because you have to modify the code spawning the process to invoke

There’s an easier way to do it: includes a function, coverage.process_startup() designed to be invoked when Python starts. It examines the COVERAGE_PROCESS_START environment variable, and if it is set, begins coverage measurement. The environment variable’s value will be used as the name of the configuration file to use.

When using this technique, be sure to set the parallel option to true so that multiple runs will each write their data to a distinct file.

Configuring Python for sub-process coverage

Measuring coverage in sub-processes is a little tricky. When you spawn a sub-process, you are invoking Python to run your program. Usually, to get coverage measurement, you have to use to run your program. Your sub-process won’t be using, so we have to convince Python to use even when not explicitly invoked.

To do that, we’ll configure Python to run a little code when it starts. That code will look for an environment variable that tells it to start coverage measurement at the start of the process.

To arrange all this, you have to do two things: set a value for the COVERAGE_PROCESS_START environment variable, and then configure Python to invoke coverage.process_startup() when Python processes start.

How you set COVERAGE_PROCESS_START depends on the details of how you create sub-processes. As long as the environment variable is visible in your sub-process, it will work.

You can configure your Python installation to invoke the process_startup function in two ways:

  1. Create or append to to add these lines:

    import coverage
  2. Create a .pth file in your Python installation containing:

    import coverage; coverage.process_startup()

The technique is cleaner, but may involve modifying an existing, since there can be only one. If there is no already, you can create it in any directory on the Python path.

The .pth technique seems like a hack, but works, and is documented behavior. On the plus side, you can create the file with any name you like so you don’t have to coordinate with other .pth files. On the minus side, you have to create the file in a system-defined directory, so you may need privileges to write it.

Note that if you use one of these techniques, you must undo them if you uninstall, since you will be trying to import it during Python start-up. Be sure to remove the change when you uninstall, or use a more defensive approach to importing it.

Signal handlers and atexit

To successfully write a coverage data file, the Python sub-process under analysis must shut down cleanly and have a chance for to run the atexit handler it registers.

For example if you send SIGTERM to end the sub-process, but your sub-process has never registered any SIGTERM handler, then a coverage file won’t be written. See the atexit docs for details of when the handler isn’t run.