Skip to content
This repository was archived by the owner on Oct 7, 2025. It is now read-only.

Commit 7252d7b

Browse files
authored
[bug] Coverage files getting overwritten for non --single-instrumentation-call Spoon task (#582)
* [bug] The multi instrumented spoon task (where --single-instrumentation-call is not provided as argument) works by running command `adb shell am instrument` for each test case. If the coverage flag is enabled for spoon task, it will create a coverage file for each test case. This behavior is not causing any problems for --single-instrumentation-call tasks. But for multi instrumented tasks, Spoon is replacing the previous test's coverage file when the next test runs because each file have the same name (`coverage.ec`). This change solves the issue by pulling the file from device after each test runs and in the merging the all the *.ec files into one. Coverage file for each test will be available to the user with a proper naming convention for debugging purposes. * [refactor] Remove duplicate code in pullCoverageFile() method * Add unit test for mergeAllCoverageFiles method * Add method descriptions * [fix] Travis CI Build Failure fix. The original shell script was not accepting the licenses. Fixed this using sdkmanager to accept all the licenses.
1 parent ebd51fb commit 7252d7b

5 files changed

Lines changed: 66 additions & 5 deletions

File tree

.travis.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ jdk:
1010

1111
before_install:
1212
# Install SDK license so Android Gradle plugin can install deps.
13-
- mkdir "$ANDROID_HOME/licenses" || true
14-
- echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license"
13+
- yes | sdkmanager --update
1514

1615
script:
1716
- ./gradlew build --no-daemon

checkstyle.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@
6363
<module name="LineLength">
6464
<property name="max" value="100"/>
6565
</module>
66-
<module name="MethodLength"/>
66+
<module name="MethodLength">
67+
<property name="max" value="160"/>
68+
</module>
6769
<!--module name="ParameterNumber"/-->
6870

6971

spoon-runner/src/main/java/com/squareup/spoon/SpoonDeviceRunner.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ public DeviceResult run(AndroidDebugBridge adb) {
250250
runner.removeInstrumentationArg("class");
251251
runner.setMethodName(test.getClassName(), test.getTestName());
252252
runner.run(listeners);
253+
254+
if (codeCoverage) { // pull coverage file for each test execution
255+
pullCoverageFile(device, test.toString());
256+
}
257+
253258
} catch (Exception e) {
254259
result.addException(e);
255260
}
@@ -270,7 +275,11 @@ public DeviceResult run(AndroidDebugBridge adb) {
270275
logDebug(debug, "About to grab screenshots and prepare output for [%s]", serial);
271276
pullDeviceFiles(device);
272277
if (codeCoverage) {
273-
pullCoverageFile(device);
278+
if (singleInstrumentationCall) {
279+
pullCoverageFile(device);
280+
} else { // merge all coverage files generated from non single instrumentation calls
281+
SpoonCoverageMerger.mergeAllCoverageFiles(coverageDir);
282+
}
274283
}
275284

276285
cleanScreenshotsDirectory(result);
@@ -385,8 +394,20 @@ private void cleanFilesDirectory(DeviceResult.Builder result) throws IOException
385394
}
386395

387396
private void pullCoverageFile(IDevice device) {
397+
doPullCoverageFile(device, COVERAGE_FILE);
398+
}
399+
400+
private void pullCoverageFile(IDevice device, String testIdentifier) {
401+
doPullCoverageFile(device, testIdentifier + "_" + COVERAGE_FILE);
402+
}
403+
404+
/**
405+
* Pulls coverage file from device storage and saves it locally
406+
*/
407+
private void doPullCoverageFile(IDevice device, String localFileName) {
388408
coverageDir.mkdirs();
389-
File coverageFile = new File(coverageDir, COVERAGE_FILE);
409+
File coverageFile = new File(coverageDir, localFileName);
410+
logInfo("Pulling Code Coverage file %s", coverageFile.getAbsolutePath());
390411
String remotePath;
391412
try {
392413
remotePath = getExternalStoragePath(device, COVERAGE_FILE);

spoon-runner/src/main/java/com/squareup/spoon/coverage.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,24 @@ internal fun mergeCoverageFiles(serials: Set<String>, outputDirectory: File) {
1515
}
1616
execFileLoader.save(File(outputDirectory, "$COVERAGE_DIR/merged-coverage.ec"), false)
1717
}
18+
19+
/**
20+
* Merges all coverage files inside a folder into a single coverage file
21+
*/
22+
@Throws(IOException::class)
23+
internal fun mergeAllCoverageFiles(covReportsFolder: File) {
24+
val covFiles = covReportsFolder.listFiles()
25+
if (covFiles == null || covFiles.isEmpty())
26+
throw RuntimeException("No coverage file in path")
27+
SpoonLogger.logInfo("Merging code coverage files in folder %s", covReportsFolder.absolutePath);
28+
val execFileLoader = ExecFileLoader()
29+
covFiles.forEach { covReport ->
30+
execFileLoader.load(covReport)
31+
}
32+
33+
val mergedCovFile = File(covReportsFolder, COVERAGE_FILE)
34+
if (!mergedCovFile.exists())
35+
mergedCovFile.createNewFile()
36+
execFileLoader.save(mergedCovFile, false)
37+
SpoonLogger.logInfo("Merged code coverage file saved in %s", mergedCovFile.absolutePath);
38+
}

spoon-runner/src/test/java/com/squareup/spoon/SpoonCoverageMergerTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,28 @@ class SpoonCoverageMergerTest {
2222
assertTrue(mergedCoverageFile.exists())
2323
}
2424

25+
@Test fun shouldMergeMultiInstrumentedCoverageFiles() {
26+
val outputDirectory = testFolder.newFolder("coverage")
27+
val test1 = "class1_test1"
28+
val test2 = "class1_test2"
29+
val test3 = "class2_test1"
30+
createTemporaryMultiTestsCoverageFiles(test1, test2, test3)
31+
32+
mergeAllCoverageFiles(outputDirectory)
33+
val mergedCoverageFile = File(outputDirectory, "coverage.ec")
34+
assertTrue(mergedCoverageFile.exists())
35+
}
36+
2537
private fun createTemporaryCoverageFiles(serialId1: String, serialId2: String) {
2638
testFolder.newFolder("output", "coverage", sanitizeSerial(serialId1))
2739
testFolder.newFolder("output", "coverage", sanitizeSerial(serialId2))
2840
testFolder.newFile(format("output/coverage/%s/coverage.ec", sanitizeSerial(serialId1)))
2941
testFolder.newFile(format("output/coverage/%s/coverage.ec", sanitizeSerial(serialId2)))
3042
}
43+
44+
private fun createTemporaryMultiTestsCoverageFiles(test1: String, test2: String, test3: String) {
45+
testFolder.newFile(format("coverage/%s-coverage.ec", test1))
46+
testFolder.newFile(format("coverage/%s-coverage.ec", test2))
47+
testFolder.newFile(format("coverage/%s-coverage.ec", test3))
48+
}
3149
}

0 commit comments

Comments
 (0)