1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.device.metric;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.ArgumentMatchers.anyInt;
24 import static org.mockito.ArgumentMatchers.anyLong;
25 import static org.mockito.ArgumentMatchers.anyString;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.doAnswer;
28 import static org.mockito.Mockito.doReturn;
29 import static org.mockito.Mockito.inOrder;
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35 
36 import com.android.tradefed.config.ConfigurationException;
37 import com.android.tradefed.config.IConfiguration;
38 import com.android.tradefed.config.OptionSetter;
39 import com.android.tradefed.device.DeviceNotAvailableException;
40 import com.android.tradefed.device.ITestDevice;
41 import com.android.tradefed.invoker.IInvocationContext;
42 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
43 import com.android.tradefed.result.ITestInvocationListener;
44 import com.android.tradefed.result.InputStreamSource;
45 import com.android.tradefed.result.LogDataType;
46 import com.android.tradefed.testtype.coverage.CoverageOptions;
47 import com.android.tradefed.testtype.suite.ModuleDefinition;
48 import com.android.tradefed.util.CommandResult;
49 import com.android.tradefed.util.CommandStatus;
50 import com.android.tradefed.util.JavaCodeCoverageFlusher;
51 import com.android.tradefed.util.MultiMap;
52 import com.android.tradefed.util.TarUtil;
53 import com.android.tradefed.util.proto.TfMetricProtoUtil;
54 
55 import com.google.common.collect.ImmutableList;
56 import com.google.common.collect.ImmutableMap;
57 import com.google.protobuf.ByteString;
58 
59 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
60 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
61 import org.jacoco.core.tools.ExecFileLoader;
62 import org.jacoco.core.data.ExecutionData;
63 import org.jacoco.core.data.ExecutionDataStore;
64 import org.jacoco.core.data.ExecutionDataWriter;
65 import org.jacoco.core.internal.data.CRC64;
66 import org.junit.After;
67 import org.junit.Before;
68 import org.junit.Rule;
69 import org.junit.Test;
70 import org.junit.rules.TemporaryFolder;
71 import org.junit.runner.RunWith;
72 import org.junit.runners.JUnit4;
73 import org.mockito.ArgumentCaptor;
74 import org.mockito.InOrder;
75 import org.mockito.Mock;
76 import org.mockito.MockitoAnnotations;
77 import org.mockito.Spy;
78 
79 import java.io.ByteArrayOutputStream;
80 import java.io.File;
81 import java.io.FileInputStream;
82 import java.io.FileOutputStream;
83 import java.io.IOException;
84 import java.io.InputStream;
85 import java.io.OutputStream;
86 import java.util.Arrays;
87 import java.util.ArrayList;
88 import java.util.HashMap;
89 import java.util.List;
90 import java.util.Map;
91 import java.util.concurrent.TimeUnit;
92 
93 /** Unit tests for {@link JavaCodeCoverageCollector}. */
94 @RunWith(JUnit4.class)
95 public class JavaCodeCoverageCollectorTest {
96 
97     private static final int PROBE_COUNT = 10;
98 
99     private static final String RUN_NAME = "SomeTest";
100     private static final int TEST_COUNT = 5;
101     private static final long ELAPSED_TIME = 1000;
102 
103     private static final String DEVICE_PATH = "/some/path/on/the/device.ec";
104     private static final ByteString COVERAGE_MEASUREMENT =
105             ByteString.copyFromUtf8("Mi estas kovrado mezurado");
106 
107     @Rule public TemporaryFolder folder = new TemporaryFolder();
108 
109     @Mock IConfiguration mMockConfiguration;
110     @Mock IInvocationContext mMockContext;
111     @Mock ITestDevice mMockDevice;
112     @Mock JavaCodeCoverageFlusher mMockFlusher;
113 
114     @Spy LogFileReader mFakeListener = new LogFileReader();
115 
116     /** Object under test. */
117     JavaCodeCoverageCollector mCodeCoverageCollector;
118 
119     CoverageOptions mCoverageOptions = null;
120     OptionSetter mCoverageOptionsSetter = null;
121     List<File> mFilesToClean;
122 
123     @Before
setUp()124     public void setUp() throws Exception {
125         MockitoAnnotations.initMocks(this);
126 
127         mCoverageOptions = new CoverageOptions();
128         mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
129         mFilesToClean = new ArrayList<>();
130 
131         when(mMockConfiguration.getCoverageOptions()).thenReturn(mCoverageOptions);
132 
133         when(mMockContext.getDevices()).thenReturn(ImmutableList.of(mMockDevice));
134         when(mMockContext.getAttributes())
135                 .thenReturn(
136                         new MultiMap(ImmutableMap.of(ModuleDefinition.MODULE_NAME, "myModule")));
137 
138         // Mock an unrooted device that has no issues enabling or disabling root.
139         when(mMockDevice.isAdbRoot()).thenReturn(false);
140         when(mMockDevice.enableAdbRoot()).thenReturn(true);
141         when(mMockDevice.disableAdbRoot()).thenReturn(true);
142 
143         mCodeCoverageCollector = new JavaCodeCoverageCollector();
144         mCodeCoverageCollector.setConfiguration(mMockConfiguration);
145     }
146 
147     @After
cleanUp()148     public void cleanUp() throws IOException {
149         for (File file : mFilesToClean) {
150             file.delete();
151         }
152     }
153 
154     @Test
testRunEnded_noCoverageEnabled_noop()155     public void testRunEnded_noCoverageEnabled_noop() throws Exception {
156         // Setup mocks.
157         HashMap<String, Metric> runMetrics = new HashMap<>();
158 
159         // Simulate a test run.
160         mCodeCoverageCollector.init(mMockContext, mFakeListener);
161         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
162         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
163 
164         // Verify testLog(..) was not called.
165         verify(mFakeListener, never())
166                 .testLog(anyString(), eq(LogDataType.COVERAGE), eq(COVERAGE_MEASUREMENT));
167     }
168 
169     @Test
testRunEnded_rootEnabled_logsCoverageMeasurement()170     public void testRunEnded_rootEnabled_logsCoverageMeasurement() throws Exception {
171         enableJavaCoverage();
172         mCoverageOptionsSetter.setOptionValue("pull-timeout", "314159");
173 
174         // Setup mocks.
175         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
176         mockCoverageFileOnDevice(DEVICE_PATH);
177         when(mMockDevice.isAdbRoot()).thenReturn(true);
178         doReturn("").when(mMockDevice).executeShellCommand(anyString());
179         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
180 
181         // Simulate a test run.
182         mCodeCoverageCollector.init(mMockContext, mFakeListener);
183         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
184         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
185 
186         // Verify timeout is set.
187         verify(mMockDevice, times(1))
188                 .executeShellV2Command(
189                         eq("find /data/misc/trace -name '*.ec' | tar -czf - -T - 2>/dev/null"),
190                         any(),
191                         any(),
192                         eq(314159L),
193                         eq(TimeUnit.MILLISECONDS),
194                         eq(1));
195 
196         // Verify testLog(..) was called with the coverage file.
197         verify(mFakeListener)
198                 .testLog(anyString(), eq(LogDataType.COVERAGE), eq(COVERAGE_MEASUREMENT));
199     }
200 
201     @Test
testRunEnded_rootEnabled_noModuleName_logsCoverageMeasurement()202     public void testRunEnded_rootEnabled_noModuleName_logsCoverageMeasurement() throws Exception {
203         enableJavaCoverage();
204 
205         // Setup mocks.
206         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
207         mockCoverageFileOnDevice(DEVICE_PATH);
208         when(mMockDevice.isAdbRoot()).thenReturn(true);
209         when(mMockContext.getAttributes()).thenReturn(new MultiMap(ImmutableMap.of()));
210         doReturn("").when(mMockDevice).executeShellCommand(anyString());
211         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
212 
213         // Simulate a test run.
214         mCodeCoverageCollector.init(mMockContext, mFakeListener);
215         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
216         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
217 
218         // Verify testLog(..) was called with the coverage file.
219         verify(mFakeListener)
220                 .testLog(anyString(), eq(LogDataType.COVERAGE), eq(COVERAGE_MEASUREMENT));
221     }
222 
223     @Test
testFailure_unableToPullFile()224     public void testFailure_unableToPullFile() throws Exception {
225         enableJavaCoverage();
226         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
227         doReturn("").when(mMockDevice).executeShellCommand(anyString());
228         doReturn(null).when(mMockDevice).pullFile(DEVICE_PATH);
229         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
230 
231         // Simulate a test run.
232         mCodeCoverageCollector.init(mMockContext, mFakeListener);
233         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
234         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
235 
236         verify(mFakeListener, never())
237                 .testLog(anyString(), eq(LogDataType.COVERAGE), any(InputStreamSource.class));
238     }
239 
240     @Test
testRunEnded_rootDisabled_enablesRootBeforePullingFiles()241     public void testRunEnded_rootDisabled_enablesRootBeforePullingFiles() throws Exception {
242         enableJavaCoverage();
243         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
244         mockCoverageFileOnDevice(DEVICE_PATH);
245         when(mMockDevice.isAdbRoot()).thenReturn(false);
246         doReturn("").when(mMockDevice).executeShellCommand(anyString());
247         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
248 
249         // Simulate a test run.
250         mCodeCoverageCollector.init(mMockContext, mFakeListener);
251         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
252         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
253 
254         InOrder inOrder = inOrder(mMockDevice);
255         inOrder.verify(mMockDevice).enableAdbRoot();
256         inOrder.verify(mMockDevice).pullFile(anyString());
257     }
258 
259     @Test
testRunEnded_rootDisabled_noLogIfCannotEnableRoot()260     public void testRunEnded_rootDisabled_noLogIfCannotEnableRoot() throws Exception {
261         enableJavaCoverage();
262         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
263         mockCoverageFileOnDevice(DEVICE_PATH);
264         when(mMockDevice.isAdbRoot()).thenReturn(false);
265         when(mMockDevice.enableAdbRoot()).thenReturn(false);
266 
267         // Simulate a test run.
268         try {
269             mCodeCoverageCollector.init(mMockContext, mFakeListener);
270             fail("An exception should have been thrown.");
271         } catch (RuntimeException e) {
272             // Expected.
273         }
274 
275         verify(mFakeListener, never())
276                 .testLog(anyString(), eq(LogDataType.COVERAGE), any(InputStreamSource.class));
277     }
278 
279     @Test
testRunEnded_rootDisabled_disablesRootAfterPullingFiles()280     public void testRunEnded_rootDisabled_disablesRootAfterPullingFiles() throws Exception {
281         enableJavaCoverage();
282         HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
283         mockCoverageFileOnDevice(DEVICE_PATH);
284         when(mMockDevice.isAdbRoot()).thenReturn(false);
285         doReturn("").when(mMockDevice).executeShellCommand(anyString());
286         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
287 
288         // Simulate a test run.
289         mCodeCoverageCollector.init(mMockContext, mFakeListener);
290         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
291         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
292 
293         InOrder inOrder = inOrder(mMockDevice);
294         inOrder.verify(mMockDevice).pullFile(anyString());
295         inOrder.verify(mMockDevice).disableAdbRoot();
296     }
297 
298     @Test
testCoverageFlush_producesMultipleMeasurements()299     public void testCoverageFlush_producesMultipleMeasurements() throws Exception {
300         enableJavaCoverage();
301 
302         Map<String, ByteString> coverageData =
303                 ImmutableMap.of(
304                         "/data/misc/trace/com.android.test1.ec",
305                         ByteString.copyFromUtf8("com.android.test1.ec"),
306                         "/data/misc/trace/com.android.test2.ec",
307                         ByteString.copyFromUtf8("com.android.test2.ec"),
308                         "/data/misc/trace/com.google.test3.ec",
309                         ByteString.copyFromUtf8("com.google.test3.ec"));
310 
311         mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
312 
313         // Setup mocks.
314         mockCoverageFileOnDevice(DEVICE_PATH);
315 
316         doReturn("").when(mMockDevice).executeShellCommand("ps -e");
317         doReturn("")
318                 .when(mMockDevice)
319                 .executeShellCommand(JavaCodeCoverageCollector.FIND_COVERAGE_FILES);
320         returnFileContentsOnShellCommand(mMockDevice, createTarGz(coverageData));
321 
322         mCodeCoverageCollector.setCoverageFlusher(mMockFlusher);
323 
324         // Simulate a test run.
325         mCodeCoverageCollector.init(mMockContext, mFakeListener);
326         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
327         Map<String, String> metric = new HashMap<>();
328         metric.put("coverageFilePath", DEVICE_PATH);
329         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
330 
331         // Verify the coverage data was logged.
332         for (ByteString contents : coverageData.values()) {
333             verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), eq(contents));
334         }
335     }
336 
337     @Test
testRunningProcess_coverageFileNotDeleted()338     public void testRunningProcess_coverageFileNotDeleted() throws Exception {
339         enableJavaCoverage();
340 
341         List<String> coverageFileList =
342                 ImmutableList.of(
343                         "/data/misc/trace/coverage1.ec",
344                         "/data/misc/trace/coverage2.ec",
345                         "/data/misc/trace/jacoco-123.mm.ec",
346                         "/data/misc/trace/jacoco-456.mm.ec");
347         String psOutput =
348                 "USER       PID   PPID  VSZ   RSS   WCHAN       PC  S NAME\n"
349                     + "bluetooth   123  1366  123    456   SyS_epoll+   0  S"
350                     + " com.android.bluetooth\n"
351                     + "radio       890     1 7890   123   binder_io+   0  S com.android.phone\n"
352                     + "root         11  1234  567   890   binder_io+   0  S not.a.java.package\n";
353 
354         // Setup mocks.
355         mockCoverageFileOnDevice(DEVICE_PATH);
356 
357         for (String additionalFile : coverageFileList) {
358             mockCoverageFileOnDevice(additionalFile);
359         }
360 
361         doReturn("").when(mMockDevice).executeShellCommand("pm list packages -a");
362         doReturn(psOutput).when(mMockDevice).executeShellCommand("ps -e");
363         doReturn(String.join("\n", coverageFileList))
364                 .when(mMockDevice)
365                 .executeShellCommand("find /data/misc/trace -name '*.ec'");
366         returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
367 
368         // Simulate a test run.
369         mCodeCoverageCollector.init(mMockContext, mFakeListener);
370         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
371         Map<String, String> metric = new HashMap<>();
372         metric.put("coverageFilePath", DEVICE_PATH);
373         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
374 
375         // Verify the correct files were deleted and some files were not deleted.
376         verify(mMockDevice).deleteFile(coverageFileList.get(0));
377         verify(mMockDevice).deleteFile(coverageFileList.get(1));
378         verify(mMockDevice, never()).deleteFile(coverageFileList.get(2));
379         verify(mMockDevice).deleteFile(coverageFileList.get(3));
380     }
381 
382     @Test
testStreamingCoverage_logsReceived()383     public void testStreamingCoverage_logsReceived() throws Exception {
384         enableJavaCoverage();
385 
386         String path1 = "path/to/coverage1.ec";
387         ByteString contents1 = ByteString.copyFromUtf8("File contents 1");
388         String path2 = "path/to/coverage2.ec";
389         ByteString contents2 = ByteString.copyFromUtf8("File contents 2");
390         File tarGz =
391                 createTarGz(
392                         ImmutableMap.of(
393                                 path1, contents1,
394                                 path2, contents2));
395 
396         // Return the tar.gz file when running the stream-compress command.
397         returnFileContentsOnShellCommand(mMockDevice, tarGz);
398 
399         // Return no data for the `ps -e` command.
400         doReturn("").when(mMockDevice).executeShellCommand(anyString());
401 
402         // Simulate a test run.
403         mCodeCoverageCollector.init(mMockContext, mFakeListener);
404         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
405         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
406 
407         // Verify that the coverage data was logged.
408         verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), eq(contents1));
409         verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), eq(contents2));
410     }
411 
412     @Test
testInitNoResetCoverage_noop()413     public void testInitNoResetCoverage_noop() throws Exception {
414         enableJavaCoverage();
415         mCoverageOptionsSetter.setOptionValue("reset-coverage-before-test", "false");
416 
417         // Run init(...).
418         mCodeCoverageCollector.init(mMockContext, mFakeListener);
419 
420         // Verify that nothing was run on the device.
421         verifyNoMoreInteractions(mMockDevice);
422     }
423 
424     @Test
testMergeSingleMeasurement_logReceived()425     public void testMergeSingleMeasurement_logReceived() throws Exception {
426         enableJavaCoverage();
427         mCoverageOptionsSetter.setOptionValue("merge-coverage", "true");
428 
429         doReturn("").when(mMockDevice).executeShellCommand(anyString());
430 
431         ByteString measurement = measurement(firstHalfCovered(JavaCodeCoverageCollector.class));
432         File tarGz = createTarGz(ImmutableMap.of("path/to/coverage.ec", measurement));
433         returnFileContentsOnShellCommand(mMockDevice, tarGz);
434 
435         // Simulate a test run.
436         mCodeCoverageCollector.init(mMockContext, mFakeListener);
437         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
438         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
439 
440         // Validate the logged coverage data.
441         ArgumentCaptor<ByteString> stream = ArgumentCaptor.forClass(ByteString.class);
442         verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), stream.capture());
443 
444         ExecFileLoader execFileLoader = new ExecFileLoader();
445         execFileLoader.load(stream.getValue().newInput());
446 
447         ExecutionDataStore execData = execFileLoader.getExecutionDataStore();
448         boolean[] firstHalf = new boolean[PROBE_COUNT];
449         for (int i = 0; i < PROBE_COUNT / 2; i++) {
450             firstHalf[i] = true;
451         }
452 
453         assertThat(execData.contains(vmName(JavaCodeCoverageCollector.class))).isTrue();
454         assertThat(getProbes(JavaCodeCoverageCollector.class, execData)).isEqualTo(firstHalf);
455     }
456 
457     @Test
testMergeMultipleMeasurements_logContainsAllData()458     public void testMergeMultipleMeasurements_logContainsAllData() throws Exception {
459         enableJavaCoverage();
460         mCoverageOptionsSetter.setOptionValue("merge-coverage", "true");
461 
462         doReturn("").when(mMockDevice).executeShellCommand(anyString());
463 
464         ByteString firstHalfCollector =
465                 measurement(firstHalfCovered(JavaCodeCoverageCollector.class));
466         ByteString secondHalfCollector =
467                 measurement(secondHalfCovered(JavaCodeCoverageCollector.class));
468         ByteString partialCollectorTest =
469                 measurement(partiallyCovered(JavaCodeCoverageCollectorTest.class));
470         File tarGz =
471                 createTarGz(
472                         ImmutableMap.of(
473                                 "JavaCodeCoverageColletor1.ec", firstHalfCollector,
474                                 "JavaCodeCoverageCollector2.ec", secondHalfCollector,
475                                 "JavaCodeCoverageCollectorTest.ec", partialCollectorTest));
476         returnFileContentsOnShellCommand(mMockDevice, tarGz);
477 
478         // Simulate a test run.
479         mCodeCoverageCollector.init(mMockContext, mFakeListener);
480         mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
481         mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
482 
483         // Validate the logged coverage data.
484         ArgumentCaptor<ByteString> stream = ArgumentCaptor.forClass(ByteString.class);
485         verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), stream.capture());
486 
487         ExecFileLoader execFileLoader = new ExecFileLoader();
488         execFileLoader.load(stream.getValue().newInput());
489 
490         ExecutionDataStore execData = execFileLoader.getExecutionDataStore();
491 
492         // Check coverage data for JavaCodeCoverageCollector. All probes should be true if the data
493         // merged successfully.
494         boolean[] fullyCovered = new boolean[PROBE_COUNT];
495         Arrays.fill(fullyCovered, Boolean.TRUE);
496 
497         assertThat(execData.contains(vmName(JavaCodeCoverageCollector.class))).isTrue();
498         assertThat(getProbes(JavaCodeCoverageCollector.class, execData)).isEqualTo(fullyCovered);
499 
500         // Check coverage data for JavaCodeCoverageCollectorTest. Only the first probe should be
501         // true.
502         boolean[] partiallyCovered = new boolean[PROBE_COUNT];
503         partiallyCovered[0] = true;
504 
505         assertThat(execData.contains(vmName(JavaCodeCoverageCollectorTest.class))).isTrue();
506         assertThat(getProbes(JavaCodeCoverageCollectorTest.class, execData))
507                 .isEqualTo(partiallyCovered);
508     }
509 
mockCoverageFileOnDevice(String devicePath)510     private void mockCoverageFileOnDevice(String devicePath)
511             throws IOException, DeviceNotAvailableException {
512         File coverageFile = folder.newFile(new File(devicePath).getName());
513 
514         try (OutputStream out = new FileOutputStream(coverageFile)) {
515             COVERAGE_MEASUREMENT.writeTo(out);
516         }
517 
518         doReturn(coverageFile).when(mMockDevice).pullFile(devicePath);
519     }
520 
vmName(Class<T> clazz)521     private static <T> String vmName(Class<T> clazz) {
522         return clazz.getName().replace('.', '/');
523     }
524 
fullyCovered(Class<T> clazz)525     private static <T> ExecutionData fullyCovered(Class<T> clazz) throws IOException {
526         boolean[] probes = new boolean[PROBE_COUNT];
527         Arrays.fill(probes, Boolean.TRUE);
528         return new ExecutionData(classId(clazz), vmName(clazz), probes);
529     }
530 
partiallyCovered(Class<T> clazz)531     private static <T> ExecutionData partiallyCovered(Class<T> clazz) throws IOException {
532         boolean[] probes = new boolean[PROBE_COUNT];
533         probes[0] = true;
534         return new ExecutionData(classId(clazz), vmName(clazz), probes);
535     }
536 
firstHalfCovered(Class<T> clazz)537     private static <T> ExecutionData firstHalfCovered(Class<T> clazz) throws IOException {
538         boolean[] probes = new boolean[PROBE_COUNT];
539         for (int i = 0; i < PROBE_COUNT / 2; i++) {
540             probes[i] = true;
541         }
542         return new ExecutionData(classId(clazz), vmName(clazz), probes);
543     }
544 
secondHalfCovered(Class<T> clazz)545     private static <T> ExecutionData secondHalfCovered(Class<T> clazz) throws IOException {
546         boolean[] probes = new boolean[PROBE_COUNT];
547         for (int i = PROBE_COUNT / 2; i < PROBE_COUNT; i++) {
548             probes[i] = true;
549         }
550         return new ExecutionData(classId(clazz), vmName(clazz), probes);
551     }
552 
classId(Class<T> clazz)553     private static <T> long classId(Class<T> clazz) throws IOException {
554         return Long.valueOf(CRC64.classId(classBytes(clazz).toByteArray()));
555     }
556 
classBytes(Class<T> clazz)557     private static <T> ByteString classBytes(Class<T> clazz) throws IOException {
558         return ByteString.readFrom(
559                 clazz.getClassLoader().getResourceAsStream(vmName(clazz) + ".class"));
560     }
561 
measurement(ExecutionData... data)562     private static ByteString measurement(ExecutionData... data) throws IOException {
563         ExecutionDataStore dataStore = new ExecutionDataStore();
564         Arrays.stream(data).forEach(dataStore::put);
565 
566         try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
567             dataStore.accept(new ExecutionDataWriter(bytes));
568             return ByteString.copyFrom(bytes.toByteArray());
569         }
570     }
571 
getProbes(Class<T> clazz, ExecutionDataStore execData)572     private static <T> boolean[] getProbes(Class<T> clazz, ExecutionDataStore execData)
573             throws IOException {
574         return execData.get(classId(clazz), vmName(clazz), PROBE_COUNT).getProbesCopy();
575     }
576 
createMetricsWithCoverageMeasurement(String devicePath)577     private static HashMap<String, Metric> createMetricsWithCoverageMeasurement(String devicePath) {
578         return TfMetricProtoUtil.upgradeConvert(ImmutableMap.of("coverageFilePath", devicePath));
579     }
580 
returnFileContentsOnShellCommand(ITestDevice device, File file)581     private static void returnFileContentsOnShellCommand(ITestDevice device, File file)
582             throws DeviceNotAvailableException, IOException {
583         doAnswer(
584                         invocation -> {
585                             OutputStream out = (OutputStream) invocation.getArgument(2);
586                             try (InputStream in = new FileInputStream(file)) {
587                                 in.transferTo(out);
588                             }
589                             return new CommandResult(CommandStatus.SUCCESS);
590                         })
591                 .when(device)
592                 .executeShellV2Command(
593                         eq(JavaCodeCoverageCollector.COMPRESS_COVERAGE_FILES),
594                         any(),
595                         any(OutputStream.class),
596                         anyLong(),
597                         any(TimeUnit.class),
598                         anyInt());
599     }
600 
createTarGz(Map<String, ByteString> fileContents)601     private File createTarGz(Map<String, ByteString> fileContents) throws IOException {
602         File tarFile = folder.newFile();
603         try (TarArchiveOutputStream out =
604                 new TarArchiveOutputStream(new FileOutputStream(tarFile))) {
605             for (Map.Entry<String, ByteString> file : fileContents.entrySet()) {
606                 TarArchiveEntry entry = new TarArchiveEntry(file.getKey());
607                 entry.setSize(file.getValue().size());
608 
609                 out.putArchiveEntry(entry);
610                 file.getValue().writeTo(out);
611                 out.closeArchiveEntry();
612             }
613             File tarGz = TarUtil.gzip(tarFile);
614             mFilesToClean.add(tarGz);
615             return tarGz;
616         } finally {
617             tarFile.delete();
618         }
619     }
620 
enableJavaCoverage()621     private void enableJavaCoverage() throws ConfigurationException {
622         mCoverageOptionsSetter.setOptionValue("coverage", "true");
623         mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "JACOCO");
624     }
625 
626     /** An {@link ITestInvocationListener} which reads test log data streams for verification. */
627     private static class LogFileReader implements ITestInvocationListener {
628         /**
629          * Reads the contents of the {@code dataStream} and forwards it to the {@link
630          * #testLog(String, LogDataType, ByteString)} method.
631          */
632         @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)633         public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
634             try (InputStream input = dataStream.createInputStream()) {
635                 testLog(dataName, dataType, ByteString.readFrom(input));
636             } catch (IOException e) {
637                 throw new RuntimeException(e);
638             }
639         }
640 
641         /** No-op method for {@link Spy} verification. */
testLog(String dataName, LogDataType dataType, ByteString data)642         public void testLog(String dataName, LogDataType dataType, ByteString data) {}
643     }
644 }
645 
646