chromium/android_webview/javatests/src/org/chromium/android_webview/test/services/MetricsBridgeServiceUnitTest.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview.test.services;

import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.EITHER_PROCESS;

import android.os.IBinder;

import androidx.test.filters.MediumTest;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.android_webview.common.services.IMetricsBridgeService;
import org.chromium.android_webview.proto.MetricsBridgeRecords.HistogramRecord;
import org.chromium.android_webview.proto.MetricsBridgeRecords.HistogramRecord.RecordType;
import org.chromium.android_webview.services.MetricsBridgeService;
import org.chromium.android_webview.test.AwActivityTestRule;
import org.chromium.android_webview.test.AwJUnit4ClassRunner;
import org.chromium.android_webview.test.OnlyRunIn;
import org.chromium.base.FileUtils;
import org.chromium.base.test.util.Batch;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.FutureTask;

/**
 * Instrumentation tests for MetricsBridgeService. These tests are batched as UNIT_TESTS because
 * they don't actually launch any services or other components.
 */
@RunWith(AwJUnit4ClassRunner.class)
@OnlyRunIn(EITHER_PROCESS) // These are unit tests
@Batch(Batch.UNIT_TESTS)
public class MetricsBridgeServiceUnitTest {
    public static final byte[] PARSING_LOG_RESULT_SUCCESS_RECORD =
            HistogramRecord.newBuilder()
                    .setRecordType(RecordType.HISTOGRAM_LINEAR)
                    .setHistogramName("Android.WebView.NonEmbeddedMetrics.ParsingLogResult")
                    .setSample(MetricsBridgeService.ParsingLogResult.SUCCESS)
                    .setMin(1)
                    .setMax(MetricsBridgeService.ParsingLogResult.COUNT)
                    .setNumBuckets(MetricsBridgeService.ParsingLogResult.COUNT + 1)
                    .build()
                    .toByteArray();

    private File mTempFile;

    @Before
    public void setUp() throws IOException {
        mTempFile = File.createTempFile("test_webview_metrics_bridge_logs", null);
    }

    @After
    public void tearDown() {
        if (mTempFile.exists()) {
            Assert.assertTrue("Failed to delete \"" + mTempFile + "\"", mTempFile.delete());
        }
    }

    @Test
    @MediumTest
    // Test that the service saves metrics records to file
    public void testSaveToFile() throws Throwable {
        HistogramRecord recordBooleanProto =
                HistogramRecord.newBuilder()
                        .setRecordType(RecordType.HISTOGRAM_BOOLEAN)
                        .setHistogramName("testSaveToFile.boolean")
                        .setSample(1)
                        .build();
        HistogramRecord recordLinearProto =
                HistogramRecord.newBuilder()
                        .setRecordType(RecordType.HISTOGRAM_LINEAR)
                        .setHistogramName("testSaveToFile.linear")
                        .setSample(123)
                        .setMin(1)
                        .setMax(1000)
                        .setNumBuckets(50)
                        .build();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        writeRecordsToStream(out, recordBooleanProto, recordLinearProto, recordBooleanProto);
        byte[] expectedData = out.toByteArray();

        // Cannot bind to service using real connection since we need to inject test file name.
        MetricsBridgeService service = new MetricsBridgeService(mTempFile);
        // Simulate starting the service by calling onCreate()
        service.onCreate();

        IBinder binder = service.onBind(null);
        IMetricsBridgeService stub = IMetricsBridgeService.Stub.asInterface(binder);
        stub.recordMetrics(recordBooleanProto.toByteArray());
        stub.recordMetrics(recordLinearProto.toByteArray());
        stub.recordMetrics(recordBooleanProto.toByteArray());

        // Block until all tasks are finished to make sure all records are written to file.
        FutureTask<Object> blockTask = service.addTaskToBlock();
        AwActivityTestRule.waitForFuture(blockTask);

        byte[] resultData = FileUtils.readStream(new FileInputStream(mTempFile));
        Assert.assertArrayEquals(
                "byte data from file is different from the expected proto byte data",
                expectedData,
                resultData);
    }

    @Test
    @MediumTest
    // Test that service recovers saved data from file, appends new records to it and
    // clears the file after a retrieve call.
    public void testRetrieveFromFile() throws Throwable {
        HistogramRecord recordBooleanProto =
                HistogramRecord.newBuilder()
                        .setRecordType(RecordType.HISTOGRAM_BOOLEAN)
                        .setHistogramName("testRecoverFromFile.boolean")
                        .setSample(1)
                        .build();
        HistogramRecord recordLinearProto =
                HistogramRecord.newBuilder()
                        .setRecordType(RecordType.HISTOGRAM_LINEAR)
                        .setHistogramName("testRecoverFromFile.linear")
                        .setSample(123)
                        .setMin(1)
                        .setMax(1000)
                        .setNumBuckets(50)
                        .build();
        // write Initial proto data To File
        writeRecordsToStream(
                new FileOutputStream(mTempFile),
                recordBooleanProto,
                recordLinearProto,
                recordBooleanProto);

        // Cannot bind to service using real connection since we need to inject test file name.
        MetricsBridgeService service = new MetricsBridgeService(mTempFile);
        // Simulate starting the service by calling onCreate()
        service.onCreate();

        IBinder binder = service.onBind(null);
        IMetricsBridgeService stub = IMetricsBridgeService.Stub.asInterface(binder);
        stub.recordMetrics(recordBooleanProto.toByteArray());
        List<byte[]> retrievedDataList = stub.retrieveNonembeddedMetrics();

        byte[][] expectedData =
                new byte[][] {
                    recordBooleanProto.toByteArray(),
                    recordLinearProto.toByteArray(),
                    recordBooleanProto.toByteArray(),
                    PARSING_LOG_RESULT_SUCCESS_RECORD,
                    recordBooleanProto.toByteArray()
                };

        // Assert file is deleted after the retrieve call
        Assert.assertFalse(
                "file should be deleted after retrieve metrics call", mTempFile.exists());
        Assert.assertNotNull("retrieved byte data from the service is null", retrievedDataList);
        Assert.assertArrayEquals(
                "retrieved byte data is different from the expected data",
                expectedData,
                retrievedDataList.toArray());
    }

    private static void writeRecordsToStream(OutputStream os, HistogramRecord... records)
            throws IOException {
        for (HistogramRecord record : records) {
            record.writeDelimitedTo(os);
        }
        os.close();
    }
}