Merge changes from topic "wait-for-staged-session"

* changes:
  Replace logic that waits for staged sessions to get ready in tests
  Abandon active staged sessions before running tests
diff --git a/tests/Android.bp b/tests/Android.bp
index 9107176..9567f3f 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -114,8 +114,8 @@
 
 java_library_host {
     name: "module_test_util",
-    srcs: ["util/**/*.java"],
-    libs: ["tradefed"],
+    srcs: ["util/**/ModuleTestUtils.java"],
+    libs: ["tradefed", "truth-prebuilt"],
 }
 
 apex {
@@ -197,3 +197,13 @@
 
     data: [":com.android.apex.cts.shim.v2_prebuilt"],
 }
+
+java_test_host {
+    name: "module_test_utils_tests",
+    srcs:  ["src/**/ModuleTestUtilsTest.java"],
+    libs: ["tradefed", "truth-prebuilt"],
+    static_libs: ["module_test_util"],
+    test_config: "module-test-utils-tests.xml",
+    test_suites: ["general-tests"],
+    data: [":com.android.apex.cts.shim.v2_prebuilt"],
+}
diff --git a/tests/module-test-utils-tests.xml b/tests/module-test-utils-tests.xml
new file mode 100644
index 0000000..6337ab9
--- /dev/null
+++ b/tests/module-test-utils-tests.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="Unit tests for ModuleTestUtils">
+    <option name="test-suite-tag" value="module_test_utils_tests" />
+    <option name="test-suite-tag" value="apct" />
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="module_test_utils_tests.jar" />
+    </test>
+</configuration>
diff --git a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
index cbe9aa0..86c73e9 100644
--- a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
@@ -58,6 +58,7 @@
             CLog.i("Apex updating is not supported on this device. Skipping setup().");
             return;
         }
+        mUtils.abandonActiveStagedSession();
         uninstallApex();
     }
 
@@ -76,13 +77,12 @@
         CLog.i("Found test apex file: " + testAppFile.getAbsoluteFile());
 
         // Install apex package
-        String installResult = getDevice().installPackage(testAppFile, false);
+        String installResult = getDevice().installPackage(testAppFile, false, "--wait");
         Assert.assertNull(
                 String.format("failed to install test app %s. Reason: %s",
                     mApexFileName, installResult),
                 installResult);
 
-        mUtils.waitForStagedSessionReady();
         ApexInfo testApexInfo = mUtils.getApexInfo(testAppFile);
         Assert.assertNotNull(testApexInfo);
 
@@ -108,6 +108,7 @@
             CLog.i("Apex updating is not supported on this device. Skipping teardown().");
             return;
         }
+        mUtils.abandonActiveStagedSession();
         uninstallApex();
     }
 
diff --git a/tests/src/com/android/tests/apex/ApexRemountTest.java b/tests/src/com/android/tests/apex/ApexRemountTest.java
index bb8aa59..7574b58 100644
--- a/tests/src/com/android/tests/apex/ApexRemountTest.java
+++ b/tests/src/com/android/tests/apex/ApexRemountTest.java
@@ -16,19 +16,19 @@
 
 package com.android.tests.apex;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
 import com.android.apex.Protos.ApexManifest;
 import com.android.tests.util.ModuleTestUtils;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.util.JsonFormat;
 
-import static org.junit.Assume.assumeTrue;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -43,12 +43,14 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ApexRemountTest extends BaseHostJUnit4Test {
     private File mSavedShimFile;
+    private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
 
-    private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
     private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
 
     @Before
     public void setUp() throws Exception {
+        mUtils.abandonActiveStagedSession();
+        mUtils.uninstallShimApexIfNecessary();
         mSavedShimFile = getDevice().pullFile(SHIM_APEX_PATH);
     }
 
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index fd23f1d..6cc95d1 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -16,17 +16,16 @@
 
 package com.android.tests.apex;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tests.util.ModuleTestUtils;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.ITestDevice.ApexInfo;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,57 +38,15 @@
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ApexRollbackTests extends BaseHostJUnit4Test {
-
-    private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
-
-    /**
-     * Uninstalls a shim apex only if its latest version is installed on /data partition (i.e.
-     * it has a version higher than {@code 1}).
-     *
-     * <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot.
-     */
-    private void uninstallShimApexIfNecessary() throws Exception {
-        if (!isApexUpdateSupported()) {
-            return;
-        }
-
-        final ITestDevice.ApexInfo shimApex = getShimApex();
-        if (shimApex.versionCode == 1) {
-            // System version is active, skipping uninstalling active apex and rebooting the device.
-            return;
-        }
-        // Non system version is active, need to uninstall it and reboot the device.
-        final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
-        if (errorMessage != null) {
-            throw new AssertionError("Failed to uninstall " + shimApex);
-        }
-        getDevice().reboot();
-        assertThat(getShimApex().versionCode).isEqualTo(1L);
-    }
-
-    /**
-     * Get {@link ITestDevice.ApexInfo} for the installed shim apex.
-     */
-    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
-        return getDevice().getActiveApexes().stream().filter(
-                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
-                    () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
-    }
-
+    private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
     /**
      * Uninstalls any version greater than 1 of shim apex and reboots the device if necessary
      * to complete the uninstall.
      */
     @After
     public void tearDown() throws Exception {
-        uninstallShimApexIfNecessary();
-    }
-
-    /**
-     * Return {@code true} if and only if device supports updating apex.
-     */
-    private boolean isApexUpdateSupported() throws Exception {
-        return "true".equals(getDevice().getProperty("ro.apex.updatable"));
+        mUtils.abandonActiveStagedSession();
+        mUtils.uninstallShimApexIfNecessary();
     }
 
     /**
@@ -97,10 +54,9 @@
      */
     @Test
     public void testAutomaticBootLoopRecovery() throws Exception {
-        assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
+        assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
 
-        ModuleTestUtils utils = new ModuleTestUtils(this);
-        File apexFile = utils.getTestFile("com.android.apex.cts.shim.v2.apex");
+        File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
 
         // To simulate an apex update that causes a boot loop, we install a
         // trigger_watchdog.rc file that arranges for a trigger_watchdog.sh
@@ -111,11 +67,9 @@
         // boot loop.
         ITestDevice device = getDevice();
         device.setProperty("persist.debug.trigger_watchdog.apex", "com.android.apex.cts.shim@2");
-        String error = device.installPackage(apexFile, false);
+        String error = device.installPackage(apexFile, false, "--wait");
         assertThat(error).isNull();
 
-        utils.waitForStagedSessionReady();
-
         // After we reboot the device, we expect the device to go into boot
         // loop from trigger_watchdog.sh. Native watchdog should detect and
         // report the boot loop, causing apexd to roll back to the previous
diff --git a/tests/src/com/android/tests/util/ModuleTestUtilsTest.java b/tests/src/com/android/tests/util/ModuleTestUtilsTest.java
new file mode 100644
index 0000000..833974f
--- /dev/null
+++ b/tests/src/com/android/tests/util/ModuleTestUtilsTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.tests.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ModuleTestUtilsTest extends BaseHostJUnit4Test {
+    private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
+    private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
+
+    @Before
+    public void setUp() throws Exception {
+        mUtils.abandonActiveStagedSession();
+        mUtils.uninstallShimApexIfNecessary();
+    }
+    /**
+     * Uninstalls any version greater than 1 of shim apex and reboots the device if necessary
+     * to complete the uninstall.
+     */
+    @After
+    public void tearDown() throws Exception {
+        mUtils.abandonActiveStagedSession();
+        mUtils.uninstallShimApexIfNecessary();
+    }
+
+    /**
+     * Unit test for {@link ModuleTestUtils#abandonActiveStagedSession()}
+     */
+    @Test
+    public void testAbandonActiveStagedSession() throws Exception {
+        File apexFile = mUtils.getTestFile(SHIM_V2);
+
+        // Install apex package
+        String installResult = getDevice().installPackage(apexFile, false, "--wait");
+        assertWithMessage(String.format("failed to install apex first time %s. Reason: %s",
+                        SHIM_V2, installResult)).that(installResult).isNull();
+
+        // Abandon ready session
+        mUtils.abandonActiveStagedSession();
+
+        // Install apex again
+        installResult = getDevice().installPackage(apexFile, false, "--wait");
+        assertWithMessage(String.format("failed to install apex again %s. Reason: %s",
+                SHIM_V2, installResult)).that(installResult).isNull();
+    }
+}
diff --git a/tests/util/com/android/tests/util/ModuleTestUtils.java b/tests/util/com/android/tests/util/ModuleTestUtils.java
index d039148..646e97a 100644
--- a/tests/util/com/android/tests/util/ModuleTestUtils.java
+++ b/tests/util/com/android/tests/util/ModuleTestUtils.java
@@ -16,9 +16,12 @@
 
 package com.android.tests.util;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.ITestDevice.ApexInfo;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -39,7 +42,7 @@
 import java.util.stream.Stream;
 
 public class ModuleTestUtils {
-
+    private static final String SHIM = "com.android.apex.cts.shim";
     private static final String APEX_INFO_EXTRACT_REGEX =
             ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*";
 
@@ -163,6 +166,60 @@
                 sessionReady);
     }
 
+    /**
+     * Abandons any staged session that is marked {@code ready}
+     */
+    public void abandonActiveStagedSession() throws DeviceNotAvailableException {
+        CommandResult res = mTest.getDevice().executeShellV2Command("pm list staged-sessions "
+                + "--only-ready --only-parent --only-sessionid");
+        assertThat(res.getStderr()).isEqualTo("");
+        String activeSessionId = res.getStdout();
+        if (activeSessionId != null && !activeSessionId.equals("")) {
+            res = mTest.getDevice().executeShellV2Command("pm install-abandon "
+                    + activeSessionId);
+            if (!res.getStderr().equals("") || res.getStatus() != CommandStatus.SUCCESS) {
+                CLog.d("Failed to abandon session " + activeSessionId
+                        + " Error: " + res.getStderr());
+            }
+        }
+    }
+
+    /**
+     * Uninstalls a shim apex only if its latest version is installed on /data partition
+     *
+     * <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot.
+     */
+    public void uninstallShimApexIfNecessary() throws Exception {
+        if (!isApexUpdateSupported()) {
+            return;
+        }
+
+        final String errorMessage = mTest.getDevice().uninstallPackage(SHIM);
+        if (errorMessage == null) {
+            CLog.i("Uninstalling shim apex");
+            mTest.getDevice().reboot();
+        } else {
+            // Most likely we tried to uninstall system version and failed. It should be fine to
+            // continue tests.
+            // TODO(b/140813980): use ApexInfo.sourceDir to decide whenever to issue an uninstall.
+            CLog.w("Failed to uninstall shim APEX: " + errorMessage);
+        }
+        assertThat(getShimApex().versionCode).isEqualTo(1L);
+    }
+
+    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
+        return mTest.getDevice().getActiveApexes().stream().filter(
+                apex -> apex.name.equals(SHIM)).findAny().orElseThrow(
+                    () -> new AssertionError("Can't find " + SHIM));
+    }
+
+    /**
+     * Return {@code true} if and only if device supports updating apex.
+     */
+    public boolean isApexUpdateSupported() throws Exception {
+        return "true".equals(mTest.getDevice().getProperty("ro.apex.updatable"));
+    }
+
     private boolean isReadyNotApplied(String sessionInfo) {
         boolean isReady = mIsSessionReadyPattern.matcher(sessionInfo).find();
         boolean isApplied = mIsSessionAppliedPattern.matcher(sessionInfo).find();