chromium/chrome/android/javatests/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceTest.java

// Copyright 2015 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.chrome.browser.crash;

import android.annotation.SuppressLint;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.content.Context;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.AdvancedMockContext;
import org.chromium.base.test.util.Feature;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.minidump_uploader.CrashTestRule;

import java.io.File;
import java.io.IOException;
import java.util.List;

/** Testcase for {@link MinidumpUploadService}. */
@RunWith(BaseJUnit4ClassRunner.class)
public class MinidumpUploadServiceTest {
    @Rule public CrashTestRule mTestRule = new CrashTestRule();

    private static final String BOUNDARY = "TESTBOUNDARY";

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testHandleForceUploadCrash_MinidumpFileExists_WithJobScheduler()
            throws IOException {
        // Set up prerequisites.
        CrashTestRule.setUpMinidumpFile(
                new File(
                        mTestRule.getCrashDir(),
                        "chromium-renderer-minidump-f297dbcba7a2d0bb.dmp0.try3"),
                BOUNDARY);
        AdvancedMockContext context =
                new MinidumpPreparationContext(
                        InstrumentationRegistry.getInstrumentation()
                                .getTargetContext()
                                .getApplicationContext());

        // Run test.
        ContextUtils.initApplicationContextForTests(context);
        MinidumpUploadServiceImpl.tryUploadCrashDumpWithLocalId("f297dbcba7a2d0bb");

        // Verify.
        final File expectedRenamedMinidumpFile =
                new File(
                        mTestRule.getCrashDir(),
                        "chromium-renderer-minidump-f297dbcba7a2d0bb.forced0.try0");
        Assert.assertTrue(
                "Should have renamed the minidump file for forced upload",
                expectedRenamedMinidumpFile.exists());
        Assert.assertTrue(
                "Should have tried to schedule an upload job",
                context.isFlagSet(TestJobScheduler.SCHEDULE_JOB_FLAG));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testHandleForceUploadCrash_SkippedMinidumpFileExists_WithJobScheduler()
            throws IOException {
        // Set up prerequisites.
        CrashTestRule.setUpMinidumpFile(
                new File(
                        mTestRule.getCrashDir(),
                        "chromium-renderer-minidump-f297dbcba7a2d0bb.skipped0.try3"),
                BOUNDARY);
        AdvancedMockContext context =
                new MinidumpPreparationContext(
                        InstrumentationRegistry.getInstrumentation()
                                .getTargetContext()
                                .getApplicationContext());

        // Run test.
        ContextUtils.initApplicationContextForTests(context);
        MinidumpUploadServiceImpl.tryUploadCrashDumpWithLocalId("f297dbcba7a2d0bb");

        // Verify.
        final File expectedRenamedMinidumpFile =
                new File(
                        mTestRule.getCrashDir(),
                        "chromium-renderer-minidump-f297dbcba7a2d0bb.forced0.try0");
        Assert.assertTrue(
                "Should have renamed the minidump file for forced upload",
                expectedRenamedMinidumpFile.exists());
        Assert.assertTrue(
                "Should have tried to schedule an upload job",
                context.isFlagSet(TestJobScheduler.SCHEDULE_JOB_FLAG));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testHandleForceUploadCrash_FileDoesntExist_WithJobScheduler() {
        // Set up prerequisites.
        AdvancedMockContext context =
                new MinidumpPreparationContext(
                        InstrumentationRegistry.getInstrumentation()
                                .getTargetContext()
                                .getApplicationContext());

        // Run test.
        MinidumpUploadServiceImpl.tryUploadCrashDumpWithLocalId("f297dbcba7a2d0bb");

        // Verify.
        Assert.assertFalse(
                "Should not have tried to schedule an upload job",
                context.isFlagSet(TestJobScheduler.SCHEDULE_JOB_FLAG));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testHandleForceUploadCrash_FileAlreadyUploaded_WithJobScheduler()
            throws IOException {
        // Set up prerequisites.
        CrashTestRule.setUpMinidumpFile(
                new File(
                        mTestRule.getCrashDir(),
                        "chromium-renderer-minidump-f297dbcba7a2d0bb.up0.try0"),
                BOUNDARY);
        AdvancedMockContext context =
                new MinidumpPreparationContext(
                        InstrumentationRegistry.getInstrumentation()
                                .getTargetContext()
                                .getApplicationContext());

        // Run test.
        MinidumpUploadServiceImpl.tryUploadCrashDumpWithLocalId("f297dbcba7a2d0bb");

        // Verify.
        Assert.assertFalse(
                "Should not have tried to schedule an upload job",
                context.isFlagSet(TestJobScheduler.SCHEDULE_JOB_FLAG));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testGetCrashType1() throws IOException {
        final File minidumpFile =
                new File(mTestRule.getCrashDir(), "chromium_renderer-123.dmp.try0");
        CrashTestRule.setUpMinidumpFile(minidumpFile, BOUNDARY, "browser");
        Assert.assertEquals(
                MinidumpUploadServiceImpl.ProcessType.BROWSER,
                MinidumpUploadServiceImpl.getCrashType(minidumpFile.getAbsolutePath()));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testGetCrashType2() throws IOException {
        final File minidumpFile =
                new File(mTestRule.getCrashDir(), "chromium_renderer-123.dmp.try0");
        CrashTestRule.setUpMinidumpFile(minidumpFile, BOUNDARY, "renderer");
        Assert.assertEquals(
                MinidumpUploadServiceImpl.ProcessType.RENDERER,
                MinidumpUploadServiceImpl.getCrashType(minidumpFile.getAbsolutePath()));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testGetCrashType3() throws IOException {
        final File minidumpFile =
                new File(mTestRule.getCrashDir(), "chromium_renderer-123.dmp.try0");
        CrashTestRule.setUpMinidumpFile(minidumpFile, BOUNDARY, "gpu-process");
        Assert.assertEquals(
                MinidumpUploadServiceImpl.ProcessType.GPU,
                MinidumpUploadServiceImpl.getCrashType(minidumpFile.getAbsolutePath()));
    }

    @Test
    @SmallTest
    @Feature({"Android-AppBase"})
    public void testGetCrashType4() throws IOException {
        final File minidumpFile =
                new File(mTestRule.getCrashDir(), "chromium_renderer-123.dmp.try0");
        CrashTestRule.setUpMinidumpFile(minidumpFile, BOUNDARY, "weird test type");
        Assert.assertEquals(
                MinidumpUploadServiceImpl.ProcessType.OTHER,
                MinidumpUploadServiceImpl.getCrashType(minidumpFile.getAbsolutePath()));
    }

    private class MinidumpPreparationContext extends AdvancedMockContext {
        /** Field used in overridden versions of startService() so we can support retries. */
        protected MinidumpUploadServiceImpl mService;

        public MinidumpPreparationContext(Context targetContext) {
            this(targetContext, null);
        }

        public MinidumpPreparationContext(
                Context targetContext, MinidumpUploadServiceImpl service) {
            super(targetContext);
            mService = service;
        }

        @Override
        public Object getSystemService(String name) {
            if (Context.JOB_SCHEDULER_SERVICE.equals(name)) {
                return new TestJobScheduler(this);
            }

            return super.getSystemService(name);
        }
    }

    /** A JobScheduler wrapper that verifies that the expected properties are set correctly. */
    private static class TestJobScheduler extends JobScheduler {
        static final String SCHEDULE_JOB_FLAG = "scheduleJobFlag";

        private final AdvancedMockContext mContext;

        TestJobScheduler(AdvancedMockContext context) {
            mContext = context;
        }

        @Override
        public void cancel(int jobId) {}

        @Override
        public void cancelAll() {}

        @Override
        @SuppressLint("WrongConstant")
        public int enqueue(JobInfo job, JobWorkItem work) {
            return 0;
        }

        @Override
        public List<JobInfo> getAllPendingJobs() {
            return null;
        }

        @Override
        public JobInfo getPendingJob(int jobId) {
            return null;
        }

        @Override
        public int schedule(JobInfo job) {
            mContext.setFlag(SCHEDULE_JOB_FLAG);
            Assert.assertEquals(TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID, job.getId());
            Assert.assertEquals(
                    ChromeMinidumpUploadJobService.class.getName(),
                    job.getService().getClassName());
            return JobScheduler.RESULT_SUCCESS;
        }
    }
}