Don’t you hate committing code and then waiting hours to find out you broke the build? Even worse is when other people commit code at a similar time to you, and you get dragged into the ‘who broke the build’ witch-hunt by pure circumstance.
If your build times are blowing out because of long test runs (greater than ten minutes), then you are most likely suffering from CI (Continuous Integration) latency and the above problems are real problems for you and your team.
Clover can help alleviate theses problems, by optimizing both unit and acceptance tests, drastically reducing the feedback time for each commit. Below is a case study of how Clover’s Test Optimization is run on the Confluence project.
With about 55 different CI Plans setup in Bamboo, the Confluence team are very serious about CI. So serious in fact, that if each build were to run end-to-end, a single commit would take over two days to be tested by each of those plans. Fortunately, Bamboo provides a pretty impressive CI-Cloud, that entails 20 different agents that run build plans in parallel. This makes it possible to get feedback in a couple of hours, as opposed to days.
Often, those few hours can mean the difference between one changeset being included in the build, or many. The main build can run for up to 40 minutes before a failure is detected. In that time, possibly multiple other commits have been made, making it more difficult to track down the root cause of the failure. 40 minutes is also long enough for a developer to be tempted by the ultimate SCM sin; commit-and-run.
For the past month, the Clover team have run a shadow plan of the Confluence trunk build which only runs the acceptance tests that cover code which was modified since the previous build. This is made possible by Clover’s per-test coverage data, that reports which tests hit which lines of code during a test run.
The optimized build (charted on the left) is configured to do a complete test run every 10 builds to refresh the per-test coverage data.
The optimized build provides faster feedback on average compared with the main build, and because it completes on average a lot faster than the main build, more builds get run in the same amount of time.
A specific case of where the Clover Optimized build failed before the full Confluence build can be seen in CCD-CONFDF-164, where it took 7 minutes to detect the Acceptance test failure, as opposed to 38 minutes in CONFFUNC-MAIN-5247. This was one case where the changeset that triggered the Optimized build was identical to the changeset that triggered the main build. Quite often, the Optimized build was started on another agent while the main build was still churning through each and every JWebUnit acceptance test.
Clover Optimized build failing in 7 minutes
The full build took 38 minutes to fail
On average the Clover Optimized Build takes just 7 minutes. This currently runs all unit and integration tests, and optimizes the long running Acceptance Tests. The main Confluence build takes 40 minutes on average to complete.
The Clover optimized build is a ‘gateway’ build for the Confluence CI-pipeline. It is the canary down the CI-mineshaft, if you will. If the optimized build smells danger, then it fails; preventing other builds from being triggered and hogging valuable CI cycles.
Greater CI Throughput == Clearer CI Results
The faster a build can run, the greater its throughput will be. What does this mean for a build that typically takes 40 minutes to run? This means that you get a much clearer picture of exactly which changeset has caused a build failure.
These next two screenshots show the Bamboo build history page for the full build plan and the Optimized build plan:
Full build results for the past two hours
You can see that build 5285 failed fairly spectacularly. However, who do we blame for this failure? Three developers made changes that triggered the build which causes a possible disturbance for all three devs as each tries to clear their name.
The Clover Optimized build paints a clearer picture of the situation:
Optimized build results for the past two hours
Over the past seven days there were 74 Full Confluence builds triggered. For the same time period there were 116 optimized builds, which represents an approximate 56% increase in build throughput.
Where’s the catch?
Of course, Test Optimization of acceptance tests is not a silver bullet.
A full build, that runs all tests regularly should still play an important role in the CI stack. This is because there are still cases where an optimized build may pass, but a full build will fail. Since Clover tracks per-test coverage for Java source files only, it will not detect which tests to run when a non-Java source file is modified. This means that if the only modification for a changeset is for a non-Java file, such as web.xml; velocity macro; pom.xml; build.xml; .jsp (and so on) then possibly no tests will be run, causing the build to pass — when it should have failed!
The aim of an optimized build plan is for it to fail faster than a full build, and also to run more often than a full build does. An optimized build should be about quantity, whereas the main build is there for quality.
What is per-test Coverage?
Per-test coverage, is a mapping of each line of code that was covered by one or more tests, back to the tests that covered the line. As an example here is a screenshot of the Clover report for a test run of Confluence’s Acceptance Tests showing all the tests that covered the “AllQuery” class:
Per-test Coverage for the AllQuery class
This shows us that “AllQuery” was covered by 16 test methods and 6 TestCases. If the class containing this code is modified in any way Clover will ensure only those 6 TestCases get run. This means the Confluence Acceptance tests can complete in just a few minutes as opposed to 40.
Per-test coverage data is also excellent for answering the question: “Is there already a test for this Class and if so, which one?”.
The Benefits of Test Optimization
The Confluence team benefit by having a Clover optimized in the following ways:
- On average, they are alerted earlier to build breakages.
- When a build does break, fewer committers are involved with the breakage, making it easier to discern who broke the build.
- Fewer CI resources on our build server are consumed by Confluence’s long running MAIN build, thereby reducing latency of other builds.