/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.helpers;

import static com.android.helpers.JankCollectionHelper.GFXINFO_COMMAND_GET;
import static com.android.helpers.JankCollectionHelper.GFXINFO_COMMAND_RESET;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.FRAME_TIME_50TH;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.FRAME_TIME_90TH;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.FRAME_TIME_95TH;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.FRAME_TIME_99TH;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.JANKY_FRAMES_COUNT;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.JANKY_FRAMES_LEGACY_COUNT;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.JANKY_FRAMES_LEGACY_PRCNT;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.JANKY_FRAMES_PRCNT;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_FRAME_DEADLINE_MISSED;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_FRAME_DEADLINE_MISSED_LEGACY;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_HIGH_INPUT_LATENCY;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_MISSED_VSYNC;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_SLOW_BITMAP_UPLOADS;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_SLOW_DRAW;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.NUM_SLOW_UI_THREAD;
import static com.android.helpers.JankCollectionHelper.GfxInfoMetric.TOTAL_FRAMES;
import static com.android.helpers.MetricUtility.constructKey;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;

import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.io.IOException;
import java.util.Map;

/** Android Unit tests for {@link JankCollectionHelper}. */
@RunWith(AndroidJUnit4.class)
public class JankCollectionHelperTest {
    private static final String GFXINFO_RESET_FORMAT =
            "\n\n** Graphics info for pid 9999 [%s] **"
                    + "\n"
                    + "\nTotal frames rendered: 0"
                    + "\nJanky frames: 0 (00.00%%)"
                    + "\nJanky frames (legacy): 0 (00.00%%)"
                    + "\n50th percentile: 0ms"
                    + "\n90th percentile: 0ms"
                    + "\n95th percentile: 0ms"
                    + "\n99th percentile: 0ms"
                    + "\nNumber Missed Vsync: 0"
                    + "\nNumber High input latency: 0"
                    + "\nNumber Slow UI thread: 0"
                    + "\nNumber Slow bitmap uploads: 0"
                    + "\nNumber Slow issue draw commands: 0"
                    + "\nNumber Frame deadline missed (legacy): 0"
                    + "\nNumber Frame deadline missed: 0";
    private static final String GFXINFO_GET_FORMAT =
            "\n\n** Graphics info for pid 9999 [%s] **"
                    + "\n"
                    + "\nTotal frames rendered: 900"
                    + "\nJanky frames: 300 (33.33%%)"
                    + "\nJanky frames (legacy): 200 (22.22%%)"
                    + "\n50th percentile: 150ms"
                    + "\n90th percentile: 190ms"
                    + "\n95th percentile: 195ms"
                    + "\n99th percentile: 199ms"
                    + "\nNumber Missed Vsync: 1"
                    + "\nNumber High input latency: 2"
                    + "\nNumber Slow UI thread: 3"
                    + "\nNumber Slow bitmap uploads: 4"
                    + "\nNumber Slow issue draw commands: 5"
                    + "\nNumber Frame deadline missed (legacy): 3"
                    + "\nNumber Frame deadline missed: 6";

    private @Mock UiDevice mUiDevice;
    private JankCollectionHelper mHelper;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mHelper = Mockito.spy(new JankCollectionHelper());
        when(mHelper.getDevice()).thenReturn(mUiDevice);
    }

    /** Test track a single, valid package. */
    @Test
    public void testCollect_valuesMatch() throws Exception {
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg1"));
        mockGetCommand("pkg1", String.format(GFXINFO_GET_FORMAT, "pkg1"));

        mHelper.addTrackedPackages("pkg1");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        assertThat(metrics.get(buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId())))
                .isEqualTo(900.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_COUNT.getMetricId())))
                .isEqualTo(300.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_PRCNT.getMetricId())))
                .isEqualTo(33.33);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_COUNT.getMetricId())))
                .isEqualTo(200.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_PRCNT.getMetricId())))
                .isEqualTo(22.22);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_50TH.getMetricId())))
                .isEqualTo(150.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_90TH.getMetricId())))
                .isEqualTo(190.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_95TH.getMetricId())))
                .isEqualTo(195.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_99TH.getMetricId())))
                .isEqualTo(199.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_MISSED_VSYNC.getMetricId())))
                .isEqualTo(1.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_HIGH_INPUT_LATENCY.getMetricId())))
                .isEqualTo(2.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_UI_THREAD.getMetricId())))
                .isEqualTo(3.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_BITMAP_UPLOADS.getMetricId())))
                .isEqualTo(4.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_DRAW.getMetricId()))).isEqualTo(5.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED.getMetricId())))
                .isEqualTo(6.0);
        assertThat(
                metrics.get(buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED_LEGACY.getMetricId())))
                        .isEqualTo(3.0);
        mHelper.stopCollecting();
    }

    /** Test track a single, valid package. */
    @Test
    public void testCollect_singlePackage() throws Exception {
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg1"));
        mockGetCommand("pkg1", String.format(GFXINFO_GET_FORMAT, "pkg1"));

        mHelper.addTrackedPackages("pkg1");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        for (String key : metrics.keySet()) {
            if (key.equals(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC)) continue;
            assertWithMessage(
                            "All keys must contains the single watched package name, except for the"
                                    + " failure count metric.")
                    .that(key)
                    .contains("pkg1");
        }
        assertThat(metrics).containsEntry(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC, 0d);
        mHelper.stopCollecting();
    }

    /** Test track multiple valid packages. */
    @Test
    public void testCollect_multiPackage() throws Exception {
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg1"));
        mockGetCommand("pkg1", String.format(GFXINFO_GET_FORMAT, "pkg1"));
        mockResetCommand("pkg2", String.format(GFXINFO_RESET_FORMAT, "pkg2"));
        mockGetCommand("pkg2", String.format(GFXINFO_GET_FORMAT, "pkg2"));
        mockResetCommand("pkg3", String.format(GFXINFO_RESET_FORMAT, "pkg3"));
        mockGetCommand("pkg3", String.format(GFXINFO_GET_FORMAT, "pkg3"));

        mHelper.addTrackedPackages("pkg1", "pkg2");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        // Assert against all keys that they only match expected packages.
        for (String key : metrics.keySet()) {
            if (key.equals(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC)) continue;
            assertWithMessage(
                            "All keys must contains one of the 2 watched package names, except for"
                                    + " the failure count metric.")
                    .that(key)
                    .containsMatch(".*pkg(1|2).*");
            assertWithMessage(
                            "The unwatched package should not be included in metrics, except for"
                                    + " the failure count metric.")
                    .that(key)
                    .doesNotContain("pkg3");
        }
        // Assert that it contains keys for both packages being watched.
        assertThat(metrics).containsKey(buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId()));
        assertThat(metrics).containsKey(buildMetricKey("pkg2", TOTAL_FRAMES.getMetricId()));
        mHelper.stopCollecting();
    }

    /** Test track all packages when unspecified. */
    @Test
    public void testCollect_allPackages() throws Exception {
        String resetOutput =
                String.join(
                        "\n",
                        String.format(GFXINFO_RESET_FORMAT, "pkg1"),
                        String.format(GFXINFO_RESET_FORMAT, "pkg2"),
                        String.format(GFXINFO_RESET_FORMAT, "pkg3"));
        String getOutput =
                String.join(
                        "\n",
                        String.format(GFXINFO_GET_FORMAT, "pkg1"),
                        String.format(GFXINFO_GET_FORMAT, "pkg2"),
                        String.format(GFXINFO_GET_FORMAT, "pkg3"));
        mockResetCommand("", resetOutput);
        mockGetCommand("", getOutput);

        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        // Assert against all keys that they only match expected packages.
        for (String key : metrics.keySet()) {
            if (key.equals(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC)) continue;
            assertWithMessage(
                            "All keys must contains one of the output package names, except for the"
                                    + " failed package count metric.")
                    .that(key)
                    .containsMatch(".*pkg(1|2|3).*");
        }
        // Assert that it contains keys for all packages being watched.
        assertThat(metrics).containsKey(buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId()));
        assertThat(metrics).containsKey(buildMetricKey("pkg2", TOTAL_FRAMES.getMetricId()));
        assertThat(metrics).containsKey(buildMetricKey("pkg3", TOTAL_FRAMES.getMetricId()));
        // No failures.
        assertThat(metrics).containsEntry(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC, 0d);
        mHelper.stopCollecting();
    }

    /** Test that it collects available fields, even if some are missing. */
    @Test
    public void testCollect_ignoreMissingFields() throws Exception {
        String missingResets =
                "\n\n** Graphics info for pid 9999 [pkg1] **"
                        + "\n"
                        + "\nTotal frames rendered: 0"
                        + "\nJanky frames: 0 (00.00%%)"
                        + "\nNumber Missed Vsync: 0"
                        + "\nNumber High input latency: 0"
                        + "\nNumber Slow UI thread: 0"
                        + "\nNumber Slow bitmap uploads: 0"
                        + "\nNumber Slow issue draw commands: 0"
                        + "\nNumber Frame deadline missed: 0";
        String missingGets =
                "\n\n** Graphics info for pid 9999 [pkg1] **"
                        + "\n"
                        + "\nTotal frames rendered: 900"
                        + "\nJanky frames: 300 (33.33%)"
                        + "\nNumber Missed Vsync: 1"
                        + "\nNumber High input latency: 2"
                        + "\nNumber Slow UI thread: 3"
                        + "\nNumber Slow bitmap uploads: 4"
                        + "\nNumber Slow issue draw commands: 5"
                        + "\nNumber Frame deadline missed: 6";

        mockResetCommand("pkg1", missingResets);
        mockGetCommand("pkg1", missingGets);

        mHelper.addTrackedPackages("pkg1");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        assertThat(metrics.keySet())
                .containsExactly(
                        buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_COUNT.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_PRCNT.getMetricId()),
                        buildMetricKey("pkg1", NUM_MISSED_VSYNC.getMetricId()),
                        buildMetricKey("pkg1", NUM_HIGH_INPUT_LATENCY.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_UI_THREAD.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_BITMAP_UPLOADS.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_DRAW.getMetricId()),
                        buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED.getMetricId()),
                        JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC);
        mHelper.stopCollecting();
    }

    /**
     * Test first values for package parsed. Gfx can provide values for every window in separate.
     * For example gfx provides separate values for: ShellDropTarget, NavigationBar0,
     * NotificationShade, StatusBar, etc. So we're looking for total value (the first one)
     */
    @Test
    public void testCollect_firstValuesMatch() throws Exception {
        final String totalApplicationResults =
                "\n\n** Graphics info for pid 9999 [pkg1] **"
                        + "\n"
                        + "\nTotal frames rendered: 900"
                        + "\nJanky frames: 300 (33.33%)"
                        + "\nJanky frames (legacy): 200 (22.22%)"
                        + "\n50th percentile: 150ms"
                        + "\n90th percentile: 190ms"
                        + "\n95th percentile: 195ms"
                        + "\n99th percentile: 199ms"
                        + "\nNumber Missed Vsync: 1"
                        + "\nNumber High input latency: 2"
                        + "\nNumber Slow UI thread: 3"
                        + "\nNumber Slow bitmap uploads: 4"
                        + "\nNumber Slow issue draw commands: 5"
                        + "\nNumber Frame deadline missed (legacy): 3"
                        + "\nNumber Frame deadline missed: 6";

        final String shellDropTargetResults =
                "\nWindow: ShellDropTarget"
                        + "\n"
                        + "\nTotal frames rendered: 0"
                        + "\nJanky frames: 0 (00.00%)"
                        + "\nJanky frames (legacy): 0 (00.00%)"
                        + "\n50th percentile: 0ms"
                        + "\n90th percentile: 0ms"
                        + "\n95th percentile: 0ms"
                        + "\n99th percentile: 0ms"
                        + "\nNumber Missed Vsync: 0"
                        + "\nNumber High input latency: 0"
                        + "\nNumber Slow UI thread: 0"
                        + "\nNumber Slow bitmap uploads: 0"
                        + "\nNumber Slow issue draw commands: 0"
                        + "\nNumber Frame deadline missed (legacy): 0"
                        + "\nNumber Frame deadline missed: 0";

        final String totalResults = totalApplicationResults + shellDropTargetResults;

        mockResetCommand("", totalResults);
        mockGetCommand("", totalResults);

        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        assertThat(metrics.get(buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId())))
                .isEqualTo(900.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_COUNT.getMetricId())))
                .isEqualTo(300.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_PRCNT.getMetricId())))
                .isEqualTo(33.33);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_COUNT.getMetricId())))
                .isEqualTo(200.0);
        assertThat(metrics.get(buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_PRCNT.getMetricId())))
                .isEqualTo(22.22);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_50TH.getMetricId())))
                .isEqualTo(150.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_90TH.getMetricId())))
                .isEqualTo(190.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_95TH.getMetricId())))
                .isEqualTo(195.0);
        assertThat(metrics.get(buildMetricKey("pkg1", FRAME_TIME_99TH.getMetricId())))
                .isEqualTo(199.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_MISSED_VSYNC.getMetricId())))
                .isEqualTo(1.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_HIGH_INPUT_LATENCY.getMetricId())))
                .isEqualTo(2.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_UI_THREAD.getMetricId())))
                .isEqualTo(3.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_BITMAP_UPLOADS.getMetricId())))
                .isEqualTo(4.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_SLOW_DRAW.getMetricId()))).isEqualTo(5.0);
        assertThat(metrics.get(buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED.getMetricId())))
                .isEqualTo(6.0);
        assertThat(
                metrics.get(buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED_LEGACY.getMetricId())))
                .isEqualTo(3.0);
        mHelper.stopCollecting();
    }

    /** Test that it collects known fields, even if some are unknown. */
    @Test
    public void testCollect_ignoreUnknownField() throws Exception {
        String extraFields =
                "\nWhatever: 1"
                        + "\nWhateverClose: 2"
                        + "\nWhateverNotSo: 3"
                        + "\nWhateverBlahs: 4";
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT + extraFields, "pkg1"));
        mockGetCommand("pkg1", String.format(GFXINFO_GET_FORMAT + extraFields, "pkg1"));

        mHelper.addTrackedPackages("pkg1");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        assertThat(metrics.keySet())
                .containsExactly(
                        buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_COUNT.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_PRCNT.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_COUNT.getMetricId()),
                        buildMetricKey("pkg1", JANKY_FRAMES_LEGACY_PRCNT.getMetricId()),
                        buildMetricKey("pkg1", FRAME_TIME_50TH.getMetricId()),
                        buildMetricKey("pkg1", FRAME_TIME_90TH.getMetricId()),
                        buildMetricKey("pkg1", FRAME_TIME_95TH.getMetricId()),
                        buildMetricKey("pkg1", FRAME_TIME_99TH.getMetricId()),
                        buildMetricKey("pkg1", NUM_MISSED_VSYNC.getMetricId()),
                        buildMetricKey("pkg1", NUM_HIGH_INPUT_LATENCY.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_UI_THREAD.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_BITMAP_UPLOADS.getMetricId()),
                        buildMetricKey("pkg1", NUM_SLOW_DRAW.getMetricId()),
                        buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED.getMetricId()),
                        buildMetricKey("pkg1", NUM_FRAME_DEADLINE_MISSED_LEGACY.getMetricId()),
                        JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC);
        mHelper.stopCollecting();
    }

    /** Test that it continues resetting even if certain packages throw for some reason. */
    @Test
    public void testCollect_delayExceptions_onReset() throws Exception {
        // Package 1 is problematic to reset, but package 2 and 3 are good.
        String cmd = String.format(GFXINFO_COMMAND_RESET, "pkg1");
        when(mUiDevice.executeShellCommand(cmd)).thenThrow(new RuntimeException());
        mockResetCommand("pkg2", String.format(GFXINFO_RESET_FORMAT, "pkg2"));
        mockResetCommand("pkg3", String.format(GFXINFO_RESET_FORMAT, "pkg3"));

        mHelper.addTrackedPackages("pkg1", "pkg2", "pkg3");
        try {
            mHelper.startCollecting();
            fail("Should have thrown an exception resetting pkg1.");
        } catch (Exception e) {
            // assert that all of the packages were reset and pass.
            InOrder inOrder = inOrder(mUiDevice);
            inOrder.verify(mUiDevice)
                    .executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg1"));
            inOrder.verify(mUiDevice)
                    .executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg2"));
            inOrder.verify(mUiDevice)
                    .executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg3"));
        }
    }

    /** Test that it reports successful packages even if certain packages throw for some reason. */
    @Test
    public void testCollect_withFailures_stillReportsSuccessfulPackages() throws Exception {
        // Package 1 is problematic to reset, but package 2 and 3 are good.
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg1"));
        mockResetCommand("pkg2", String.format(GFXINFO_RESET_FORMAT, "pkg2"));
        mockResetCommand("pkg3", String.format(GFXINFO_RESET_FORMAT, "pkg3"));
        String cmd = String.format(GFXINFO_COMMAND_GET, "pkg1");
        when(mUiDevice.executeShellCommand(cmd)).thenThrow(new RuntimeException());
        mockGetCommand("pkg2", String.format(GFXINFO_GET_FORMAT, "pkg2"));
        mockGetCommand("pkg3", String.format(GFXINFO_GET_FORMAT, "pkg3"));

        mHelper.addTrackedPackages("pkg1", "pkg2", "pkg3");
        mHelper.startCollecting();
        Map<String, Double> metrics = mHelper.getMetrics();
        // assert that all of the packages were reset and gotten and pass.
        InOrder inOrder = inOrder(mUiDevice);
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg1"));
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg2"));
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_RESET, "pkg3"));
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_GET, "pkg1"));
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_GET, "pkg2"));
        inOrder.verify(mUiDevice).executeShellCommand(String.format(GFXINFO_COMMAND_GET, "pkg3"));
        // Assert that we got metrics for pkg2 and pkg3, but not pkg1.
        // We don't verify the full set of metrics here since it's been checked in other tests.
        assertThat(metrics).doesNotContainKey(buildMetricKey("pkg1", TOTAL_FRAMES.getMetricId()));
        assertThat(metrics).containsKey(buildMetricKey("pkg2", TOTAL_FRAMES.getMetricId()));
        assertThat(metrics).containsKey(buildMetricKey("pkg3", TOTAL_FRAMES.getMetricId()));
        // We should report that one package failed to collect.
        assertThat(metrics).containsEntry(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC, 1d);
    }

    /** Test that it fails if the {@code gfxinfo} metrics cannot be cleared. */
    @Test
    public void testFailures_cannotClear() throws Exception {
        String cmd = String.format(JankCollectionHelper.GFXINFO_COMMAND_RESET, "");
        when(mUiDevice.executeShellCommand(cmd)).thenReturn("");
        try {
            mHelper.startCollecting();
            fail("Should have thrown an exception.");
        } catch (RuntimeException e) {
            // pass
        }
    }

    /** Test that it fails when encountering an {@code IOException} on reset. */
    @Test
    public void testFailures_ioFailure() throws Exception {
        String cmd = String.format(JankCollectionHelper.GFXINFO_COMMAND_RESET, "");
        when(mUiDevice.executeShellCommand(cmd)).thenThrow(new IOException());
        try {
            mHelper.startCollecting();
            fail("Should have thrown an exception.");
        } catch (RuntimeException e) {
            // pass
        }
    }

    /** Test that it fails when the package does not show up on reset. */
    @Test
    public void testFailures_noPackageOnReset() throws Exception {
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg2"));

        mHelper.addTrackedPackages("pkg1");
        try {
            mHelper.startCollecting();
            fail("Should have thrown an exception.");
        } catch (RuntimeException e) {
            // pass
        }
    }

    /** No metrics should be collected if the package does not show up on get. */
    @Test
    public void testFailures_noPackageOnGetReportsNoMetricsExceptFailureCount() throws Exception {
        mockResetCommand("pkg1", String.format(GFXINFO_RESET_FORMAT, "pkg1"));
        mockGetCommand("pkg1", String.format(GFXINFO_GET_FORMAT, "pkg2"));

        mHelper.addTrackedPackages("pkg1");
        mHelper.startCollecting();
        assertThat(mHelper.getMetrics())
                .containsExactly(JankCollectionHelper.FAILED_PACKAGES_COUNT_METRIC, 1d);
    }

    private String buildMetricKey(String pkg, String id) {
        return constructKey(JankCollectionHelper.GFXINFO_METRICS_PREFIX, pkg, id);
    }

    private void mockResetCommand(String pkg, String output) throws IOException {
        String cmd = String.format(GFXINFO_COMMAND_RESET, pkg.isEmpty() ? "--" : pkg);
        when(mUiDevice.executeShellCommand(cmd)).thenReturn(output);
    }

    private void mockGetCommand(String pkg, String output) throws IOException {
        String cmd = String.format(GFXINFO_COMMAND_GET, pkg);
        when(mUiDevice.executeShellCommand(cmd)).thenReturn(output);
    }
}
