1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview.test.services;
6 
7 import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.SINGLE_PROCESS;
8 
9 import android.os.IBinder;
10 
11 import androidx.test.filters.MediumTest;
12 
13 import org.junit.After;
14 import org.junit.Assert;
15 import org.junit.Before;
16 import org.junit.Test;
17 import org.junit.runner.RunWith;
18 
19 import org.chromium.android_webview.common.services.IMetricsBridgeService;
20 import org.chromium.android_webview.proto.MetricsBridgeRecords.HistogramRecord;
21 import org.chromium.android_webview.proto.MetricsBridgeRecords.HistogramRecord.RecordType;
22 import org.chromium.android_webview.services.MetricsBridgeService;
23 import org.chromium.android_webview.test.AwActivityTestRule;
24 import org.chromium.android_webview.test.AwJUnit4ClassRunner;
25 import org.chromium.android_webview.test.OnlyRunIn;
26 import org.chromium.base.FileUtils;
27 import org.chromium.base.test.util.Batch;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.OutputStream;
35 import java.util.List;
36 import java.util.concurrent.FutureTask;
37 
38 /**
39  * Instrumentation tests for MetricsBridgeService. These tests are batched as UNIT_TESTS because
40  * they don't actually launch any services or other components.
41  */
42 @RunWith(AwJUnit4ClassRunner.class)
43 @OnlyRunIn(SINGLE_PROCESS)
44 @Batch(Batch.UNIT_TESTS)
45 public class MetricsBridgeServiceUnitTest {
46     public static final byte[] PARSING_LOG_RESULT_SUCCESS_RECORD =
47             HistogramRecord.newBuilder()
48                     .setRecordType(RecordType.HISTOGRAM_LINEAR)
49                     .setHistogramName("Android.WebView.NonEmbeddedMetrics.ParsingLogResult")
50                     .setSample(MetricsBridgeService.ParsingLogResult.SUCCESS)
51                     .setMin(1)
52                     .setMax(MetricsBridgeService.ParsingLogResult.COUNT)
53                     .setNumBuckets(MetricsBridgeService.ParsingLogResult.COUNT + 1)
54                     .build()
55                     .toByteArray();
56 
57     public static final byte[] RETRIEVE_METRICS_TASK_STATUS_SUCCESS_RECORD =
58             HistogramRecord.newBuilder()
59                     .setRecordType(RecordType.HISTOGRAM_LINEAR)
60                     .setHistogramName(
61                             "Android.WebView.NonEmbeddedMetrics.RetrieveMetricsTaskStatus")
62                     .setSample(MetricsBridgeService.RetrieveMetricsTaskStatus.SUCCESS)
63                     .setMin(1)
64                     .setMax(MetricsBridgeService.RetrieveMetricsTaskStatus.COUNT)
65                     .setNumBuckets(MetricsBridgeService.RetrieveMetricsTaskStatus.COUNT + 1)
66                     .build()
67                     .toByteArray();
68 
69     private File mTempFile;
70 
71     @Before
setUp()72     public void setUp() throws IOException {
73         mTempFile = File.createTempFile("test_webview_metrics_bridge_logs", null);
74     }
75 
76     @After
tearDown()77     public void tearDown() {
78         if (mTempFile.exists()) {
79             Assert.assertTrue("Failed to delete \"" + mTempFile + "\"", mTempFile.delete());
80         }
81     }
82 
83     @Test
84     @MediumTest
85     // Test that the service saves metrics records to file
testSaveToFile()86     public void testSaveToFile() throws Throwable {
87         HistogramRecord recordBooleanProto = HistogramRecord.newBuilder()
88                                                      .setRecordType(RecordType.HISTOGRAM_BOOLEAN)
89                                                      .setHistogramName("testSaveToFile.boolean")
90                                                      .setSample(1)
91                                                      .build();
92         HistogramRecord recordLinearProto = HistogramRecord.newBuilder()
93                                                     .setRecordType(RecordType.HISTOGRAM_LINEAR)
94                                                     .setHistogramName("testSaveToFile.linear")
95                                                     .setSample(123)
96                                                     .setMin(1)
97                                                     .setMax(1000)
98                                                     .setNumBuckets(50)
99                                                     .build();
100         ByteArrayOutputStream out = new ByteArrayOutputStream();
101         writeRecordsToStream(out, recordBooleanProto, recordLinearProto, recordBooleanProto);
102         byte[] expectedData = out.toByteArray();
103 
104         // Cannot bind to service using real connection since we need to inject test file name.
105         MetricsBridgeService service = new MetricsBridgeService(mTempFile);
106         // Simulate starting the service by calling onCreate()
107         service.onCreate();
108 
109         IBinder binder = service.onBind(null);
110         IMetricsBridgeService stub = IMetricsBridgeService.Stub.asInterface(binder);
111         stub.recordMetrics(recordBooleanProto.toByteArray());
112         stub.recordMetrics(recordLinearProto.toByteArray());
113         stub.recordMetrics(recordBooleanProto.toByteArray());
114 
115         // Block until all tasks are finished to make sure all records are written to file.
116         FutureTask<Object> blockTask = service.addTaskToBlock();
117         AwActivityTestRule.waitForFuture(blockTask);
118 
119         byte[] resultData = FileUtils.readStream(new FileInputStream(mTempFile));
120         Assert.assertArrayEquals(
121                 "byte data from file is different from the expected proto byte data", expectedData,
122                 resultData);
123     }
124 
125     @Test
126     @MediumTest
127     // Test that service recovers saved data from file, appends new records to it and
128     // clears the file after a retrieve call.
testRetrieveFromFile()129     public void testRetrieveFromFile() throws Throwable {
130         HistogramRecord recordBooleanProto =
131                 HistogramRecord.newBuilder()
132                         .setRecordType(RecordType.HISTOGRAM_BOOLEAN)
133                         .setHistogramName("testRecoverFromFile.boolean")
134                         .setSample(1)
135                         .build();
136         HistogramRecord recordLinearProto = HistogramRecord.newBuilder()
137                                                     .setRecordType(RecordType.HISTOGRAM_LINEAR)
138                                                     .setHistogramName("testRecoverFromFile.linear")
139                                                     .setSample(123)
140                                                     .setMin(1)
141                                                     .setMax(1000)
142                                                     .setNumBuckets(50)
143                                                     .build();
144         // write Initial proto data To File
145         writeRecordsToStream(new FileOutputStream(mTempFile), recordBooleanProto, recordLinearProto,
146                 recordBooleanProto);
147 
148         // Cannot bind to service using real connection since we need to inject test file name.
149         MetricsBridgeService service = new MetricsBridgeService(mTempFile);
150         // Simulate starting the service by calling onCreate()
151         service.onCreate();
152 
153         IBinder binder = service.onBind(null);
154         IMetricsBridgeService stub = IMetricsBridgeService.Stub.asInterface(binder);
155         stub.recordMetrics(recordBooleanProto.toByteArray());
156         List<byte[]> retrievedDataList = stub.retrieveNonembeddedMetrics();
157 
158         byte[][] expectedData = new byte[][] {recordBooleanProto.toByteArray(),
159                 recordLinearProto.toByteArray(), recordBooleanProto.toByteArray(),
160                 PARSING_LOG_RESULT_SUCCESS_RECORD, recordBooleanProto.toByteArray(),
161                 RETRIEVE_METRICS_TASK_STATUS_SUCCESS_RECORD};
162 
163         // Assert file is deleted after the retrieve call
164         Assert.assertFalse(
165                 "file should be deleted after retrieve metrics call", mTempFile.exists());
166         Assert.assertNotNull("retrieved byte data from the service is null", retrievedDataList);
167         Assert.assertArrayEquals("retrieved byte data is different from the expected data",
168                 expectedData, retrievedDataList.toArray());
169     }
170 
writeRecordsToStream(OutputStream os, HistogramRecord... records)171     private static void writeRecordsToStream(OutputStream os, HistogramRecord... records)
172             throws IOException {
173         for (HistogramRecord record : records) {
174             record.writeDelimitedTo(os);
175         }
176         os.close();
177     }
178 }