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 }