[pm/incremental] unit tests for state transitions

Test: atest IncrementalStatesTest
BUG: 168043976
Change-Id: I6816c0dbc0822bb7d8312ca6c97168209af1e0da
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
new file mode 100644
index 0000000..62e135b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2020 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.server.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.PackageManager;
+import android.os.ConditionVariable;
+import android.os.incremental.IStorageHealthListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit tests for {@link IncrementalStates}.
+ * Run with: atest -c FrameworksServicesTests:com.android.server.pm.IncrementalStatesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class IncrementalStatesTest {
+    private IncrementalStates mIncrementalStates;
+    private ConditionVariable mUnstartableCalled = new ConditionVariable();
+    private ConditionVariable mStartableCalled = new ConditionVariable();
+    private ConditionVariable mFullyLoadedCalled = new ConditionVariable();
+    private AtomicInteger mUnstartableReason = new AtomicInteger(0);
+    private static final int WAIT_TIMEOUT_MILLIS = 1000; /* 1 second */
+    private IncrementalStates.Callback mCallback = new IncrementalStates.Callback() {
+        @Override
+        public void onPackageUnstartable(int reason) {
+            mUnstartableCalled.open();
+            mUnstartableReason.set(reason);
+        }
+
+        @Override
+        public void onPackageStartable() {
+            mStartableCalled.open();
+        }
+
+        @Override
+        public void onPackageFullyLoaded() {
+            mFullyLoadedCalled.open();
+        }
+    };
+
+    /**
+     * Setup the tests as if the package has just been committed.
+     * By default the package is now startable and is loading.
+     */
+    @Before
+    public void setUp() {
+        mIncrementalStates = new IncrementalStates();
+        assertFalse(mIncrementalStates.isStartable());
+        mIncrementalStates.setCallback(mCallback);
+        mIncrementalStates.onCommit(true);
+        // Test that package is now startable and loading
+        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.isLoading());
+        mStartableCalled.close();
+        mUnstartableCalled.close();
+        mFullyLoadedCalled.close();
+    }
+
+    /**
+     * Test that startable state changes to false when Incremental Storage is unhealthy.
+     */
+    @Test
+    public void testStartableTransition_IncrementalStorageUnhealthy() {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+        // Test that package is now unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get());
+    }
+
+    /**
+     * Test that the package is still startable when Incremental Storage has pending reads.
+     */
+    @Test
+    public void testStartableTransition_IncrementalStorageReadsPending()
+            throws InterruptedException {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_READS_PENDING);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that the package is still startable when Incremental Storage is at blocked status.
+     */
+    @Test
+    public void testStartableTransition_IncrementalStorageBlocked() {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_BLOCKED);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that the package is still startable when Data Loader has unknown transportation issues.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderTransportError() {
+        mIncrementalStates.onStreamStatusChanged(
+                IDataLoaderStatusListener.STREAM_TRANSPORT_ERROR);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Data Loader has data integrity issues.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderIntegrityError() {
+        mIncrementalStates.onStreamStatusChanged(
+                IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+        // Test that package is now unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
+                mUnstartableReason.get());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Data Loader has data source issues.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderSourceError() {
+        mIncrementalStates.onStreamStatusChanged(
+                IDataLoaderStatusListener.STREAM_SOURCE_ERROR);
+        // Test that package is now unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
+                mUnstartableReason.get());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Data Loader hits limited storage while
+     * Incremental storage has a pending reads.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderStorageErrorWhenIncrementalStoragePending()
+            throws InterruptedException {
+        mIncrementalStates.onStreamStatusChanged(
+                IDataLoaderStatusListener.STREAM_STORAGE_ERROR);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_READS_PENDING);
+        // Test that package is now unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE,
+                mUnstartableReason.get());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Data Loader hits limited storage while
+     * Incremental storage is at blocked status.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderStorageErrorWhenIncrementalStorageBlocked()
+            throws InterruptedException {
+        mIncrementalStates.onStreamStatusChanged(
+                IDataLoaderStatusListener.STREAM_STORAGE_ERROR);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_BLOCKED);
+        // Test that package is now unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE,
+                mUnstartableReason.get());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Incremental Storage is unhealthy, and it
+     * becomes startable again when Incremental Storage is healthy again.
+     */
+    @Test
+    public void testStartableTransition_IncrementalStorageUnhealthyBackToHealthy()
+            throws InterruptedException {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+        // Test that package is unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_OK);
+        // Test that package is now startable
+        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that the package becomes unstartable when Data Loader has data integrity issue, and it
+     * becomes startable again when Data Loader is healthy again.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderUnhealthyBackToHealthy()
+            throws InterruptedException {
+        mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+        // Test that package is unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+
+        mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_HEALTHY);
+        // Test that package is now startable
+        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that the package becomes unstartable when both Incremental Storage and Data Loader
+     * are unhealthy, and it becomes startable again when both Incremental Storage and Data Loader
+     * are healthy again.
+     */
+    @Test
+    public void testStartableTransition_DataLoaderAndIncrementalStorageUnhealthyBackToHealthy()
+            throws InterruptedException {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+        mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+        // Test that package is unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+
+        mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_HEALTHY);
+        // Test that package is still unstartable
+        assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        mIncrementalStates.onStorageHealthStatusChanged(IStorageHealthListener.HEALTH_STATUS_OK);
+        // Test that package is now startable
+        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that when loading progress is 1, the package becomes fully loaded, and the change of
+     * Incremental Storage health status does not affect the startable state.
+     */
+    @Test
+    public void testStartableTransition_HealthStatusChangeWhenFullyLoaded()
+            throws InterruptedException {
+        mIncrementalStates.setProgress(1.0f);
+        // Test that package is now fully loaded
+        assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isLoading());
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+    }
+
+    /**
+     * Test that when loading progress is 1, the package becomes fully loaded, and if the package
+     * was unstartable, it becomes startable.
+     */
+    @Test
+    public void testLoadingTransition_FullyLoadedWhenUnstartable() throws InterruptedException {
+        mIncrementalStates.onStorageHealthStatusChanged(
+                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+        // Test that package is unstartable
+        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        // Test that package is still loading
+        assertTrue(mIncrementalStates.isLoading());
+
+        mIncrementalStates.setProgress(0.5f);
+        // Test that package is still unstartable
+        assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isStartable());
+        mIncrementalStates.setProgress(1.0f);
+        // Test that package is now startable
+        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        // Test that package is now fully loaded
+        assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertFalse(mIncrementalStates.isLoading());
+    }
+}