Code Coverage: Finding Paths
Picking up from where we left last time, in this second article we will look at some upcoming functionality in Xdebug. Sebastian has been pressuring me for years to add branch and path coverage to Xdebug, with issue #1034. In the post I will show you what "branch and path coverage" is, and how it helps.
In the previous post, I showed you a trivial script as an example for code coverage. Let's have a look at a different script this time (article2-test.php):
<?php
function ifthenelse( $a, $b )
{
if ($a) {
echo "A HIT\n";
}
if ($b) {
echo "B HIT\n";
}
}
We now run this through PHP CodeCoverage (article2.php):
<?php
require 'vendor/autoload.php';
include 'article2-test.php';
$coverage = new PHP_CodeCoverage;
$coverage->start('coverage1');
ifthenelse( true, false );
$coverage->stop();
$coverage->start('coverage2');
ifthenelse( false, true );
$coverage->stop();
$writer = new PHP_CodeCoverage_Report_HTML;
$writer->process($coverage, '/tmp/code-coverage-article');
?>
Which outputs:
And this nicely tells you that all your lines have been executed. However, you have not tested everything. With two if statements, there are four possible paths through your code:
|
$a |
$b |
|
false |
false |
|
false |
true |
|
true |
false |
|
true |
true |
But we have only tested for the second and the third cases. Because we are doing line coverage, this sort of coverage issues don't show up. In order to address this, we need to do branch and path coverage.
I wrote in the previous article that Xdebug follows all branches to find dead code during code coverage, but it never did really more with that. So, again, I started experimenting through VLD to see how I could get it to list all possible branches (easy) and paths through a function or method.
So now VLD would say the following (besides dumping the opcodes) about the code:
filename: /home/httpd/html/test/xdebug/code-coverage/article2-test.php function name: ifthenelse … branch: # 0; line: 2- 4; sop: 0; eop: 4; out1: 5; out2: 8 branch: # 5; line: 5- 6; sop: 5; eop: 7; out1: 8 branch: # 8; line: 7- 7; sop: 8; eop: 9; out1: 10; out2: 13 branch: # 10; line: 8- 9; sop: 10; eop: 12; out1: 13 branch: # 13; line: 10- 10; sop: 13; eop: 14; out1: -2 path #1: 0, 5, 8, 10, 13, path #2: 0, 5, 8, 13, path #3: 0, 8, 10, 13, path #4: 0, 8, 13, End of function ifthenelse
It's a bit difficult to see what goes on here, but in general there are five branches and four paths. The first two branches (0 and 5) are for the first if statement, the second two branches (8 and 10) are for the second if statement and the last branch (13) is the end of function, with an implicit return to the calling function. Branches are named after the opcode entry that forms the start of a branch.
Seeing the different branches is probably easier to see in a graph, which VLD can also produce (using the vld.save_paths=1 php.ini setting). This produces a paths.dot file in /tmp which we can convert to an image with GraphViz' dot tool:
dot -Tpng /tmp/paths.dot > /tmp/paths.png
And then this graph looks like:
With the functionaly added to VLD, the next step was adding this code to Xdebug. Xdebug needs to know about all the paths, but also need figure out which branches and paths are actually followed. It needs to overload every opcode (PHP's smallest execution unit) handler, as any of them could be at the start of a branch. Overloading each opcode handler makes running your code, a lot slower unfortunately when with branch/path coverage is enabled.
AlthoughPHP_CodeCoverage does not support it yet, it is possible to visualize the new path coverage with help from some helper scripts in Xdebug's contrib directory. As a similar example as above, the test script would look like:
<?php
require '/home/derick/dev/php/xdebug-xdebug/contrib/branch-coverage-to-dot.php';
include 'article2-test.php';
xdebug_start_code_coverage(
XDEBUG_CC_DEAD_CODE |
XDEBUG_CC_UNUSED |
XDEBUG_CC_BRANCH_CHECK
);
ifthenelse( true, false );
ifthenelse( false, true );
$info = xdebug_get_code_coverage();
file_put_contents('/tmp/paths.dot', branch_coverage_to_dot( $info ) );
?>
This again creates a paths.dot file that we can convert to an image just like above:
dot -Tpng /tmp/paths.dot > /tmp/paths-covered.png
And then this graph looks like:
This clearly shows we have only covered two of the four possible paths through this particular function. PHP_CodeCoverage has not been updated yet to include this new functionality, so I made a mock-up in the mean while:
Now it's just waiting until Sebastian (or somebody else) has time to upgrade PHP_CodeCoverage to show the branch and path coverage. Happy hacking!
Comments
That's a very cool change. Wishing you all the best in having it implemented. I can't wait until I have to write exponentially more tests.
Has an issue already been opened for this over at the PHPUnit github repo? That might help to raise visibility, or allow people who want it bad enough to add a bounty to it (https://www.bountysource.com/ comes to mind, amongst others...)
From how i read it, and looking at the linked issue, this already has been implemented (Oh happy joy!)
Now Derick can start bugging Sebastian in return :)
@Oneway I think we mean the same thing... that this has not yet been implemented in PHPUnit_CodeCoverage. Unless I am missing something in which case I would love to know the issue URL :-)
I've completed part of the task Sebastian did and this PR https://github.com/sebastianbergmann/php-code-coverage/pull/400 contains a functional branch coverage output for Text and HTML reports.
I hope to complete Clover report soon.
Life Line
Merge branch 'xdebug_3_5'
Merged pull request #1071
Fixed issue #2411: Native Path Mapping is not applied to the initial …
Created 2 waste_baskets; Updated 3 waste_baskets, 2 benches, and 2 other objects; Deleted a waste_basket
I walked 7.9km in 1h45m36s
RE: https://phpc.social/@phpc_tv/116274041642323081
Now that phpc.tv and phpc.social are part of the same umbrella, I've upped my yearly contributions to their Open Collective: https://opencollective.com/phpcommunity/projects/phpc-social
Merge branch 'xdebug_3_5'
Merged pull request #1070
I walked 7.2km in 1h10m26s
Fixed issue #2405: Handle minimum path in .xdebug directory discovery
I've published a new blog post: "Human Creations", on the difference in content generation by LLMs, and the creation of text, art and code by humans.
You can find it at https://derickrethans.nl/human-creations.html or at @blog
I walked 7.8km in 1h38m32s
RE: https://phpc.social/@afilina/116274024588235234
It's good to see that more and more people are realising that the Web can be for-good, without all the enshittification.
That's why I'm happy to see endeavours like phpc.tv springing up, and helping out where I can.
Taking back the control of how the Web is for people, by people, without big tech making it all shit.
Created a waste_basket; Updated 5 crossings and a bicycle_parking
I walked 10.7km in 2h35m10s
If you're in the UK (south, preferably), the International Space Station is going to be visible straight above you in about half an hour!
If you're further north than London, it moves further to the south.
Updated 2 crossings
Created 3 crossings, 2 waste_baskets, and 2 trees; Updated a crossing
Created 2 post_boxes and a crossing; Updated a newsagent shop
Updated a crossing
Updated a post_box
Created a bench; Updated a bench
Created an information; Updated 2 benches
Updated a crossing and a supermarket shop
Confirmed a restaurant


Shortlink
This article has a short URL available: https://drck.me/pathcoverage-blb