Move STS utilities from test/sts and cts into sts-host-util.

Test: m cts
Bug: 238373261

Change-Id: Id3f7ca79d73e8b56b8546185fdcfc00c6b740de8
Merged-In: Id3f7ca79d73e8b56b8546185fdcfc00c6b740de8
diff --git a/libraries/sts-common-util/apps/MainlineModuleDetector/Android.bp b/libraries/sts-common-util/apps/MainlineModuleDetector/Android.bp
new file mode 100644
index 0000000..2cba39f
--- /dev/null
+++ b/libraries/sts-common-util/apps/MainlineModuleDetector/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "MainlineModuleDetector",
+    defaults: ["cts_defaults"],
+    static_libs: ["compatibility-device-util-axt"],
+    // Disable dexpreopt and <uses-library> check for test.
+    enforce_uses_libs: false,
+    dex_preopt: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
diff --git a/libraries/sts-common-util/apps/MainlineModuleDetector/AndroidManifest.xml b/libraries/sts-common-util/apps/MainlineModuleDetector/AndroidManifest.xml
new file mode 100644
index 0000000..dce1cae
--- /dev/null
+++ b/libraries/sts-common-util/apps/MainlineModuleDetector/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.mainlinemoduledetector"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <application>
+        <activity android:name=".MainlineModuleDetector"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libraries/sts-common-util/apps/MainlineModuleDetector/src/com/android/cts/mainlinemoduledetector/MainlineModuleDetector.java b/libraries/sts-common-util/apps/MainlineModuleDetector/src/com/android/cts/mainlinemoduledetector/MainlineModuleDetector.java
new file mode 100644
index 0000000..01c02c7
--- /dev/null
+++ b/libraries/sts-common-util/apps/MainlineModuleDetector/src/com/android/cts/mainlinemoduledetector/MainlineModuleDetector.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cts.mainlinemoduledetector;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.compatibility.common.util.mainline.MainlineModule;
+import com.android.compatibility.common.util.mainline.ModuleDetector;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MainlineModuleDetector extends Activity {
+
+    private static final String LOG_TAG = "MainlineModuleDetector";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            PackageManager pm = getApplicationContext().getPackageManager();
+            Set<MainlineModule> modules = ModuleDetector.getPlayManagedModules(pm);
+            Set<String> moduleNames = new HashSet<>();
+            for (MainlineModule module : modules) {
+                moduleNames.add(module.packageName);
+            }
+            Log.i(LOG_TAG, "Play managed modules are: <" + String.join(",", moduleNames) + ">");
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Failed to retrieve modules.", e);
+        }
+        this.finish();
+    }
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/HostsideMainlineModuleDetector.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/HostsideMainlineModuleDetector.java
new file mode 100644
index 0000000..eeec69c
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/HostsideMainlineModuleDetector.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sts.common;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HostsideMainlineModuleDetector {
+    private static final String LOG_TAG = "MainlineModuleDetector";
+
+    private BaseHostJUnit4Test context;
+
+    private static ImmutableSet<String> playManagedModules;
+
+    public HostsideMainlineModuleDetector(BaseHostJUnit4Test context) {
+        this.context = context;
+    }
+
+    public synchronized Set<String> getPlayManagedModules() throws Exception {
+        if (playManagedModules == null) {
+            context.getDevice().executeShellV2Command("logcat -c");
+            String output =
+                    context.getDevice()
+                            .executeShellV2Command(
+                                    "am start"
+                                        + " com.android.cts.mainlinemoduledetector/.MainlineModuleDetector")
+                            .getStdout();
+            Log.logAndDisplay(Log.LogLevel.INFO, LOG_TAG, "am output: " + output);
+            Thread.sleep(5 * 1000L);
+            String logcat =
+                    context.getDevice()
+                            .executeShellV2Command("logcat -d -s MainlineModuleDetector:I")
+                            .getStdout();
+            Log.logAndDisplay(Log.LogLevel.INFO, LOG_TAG, "Found logcat output: " + logcat);
+            Matcher matcher = Pattern.compile("Play managed modules are: <(.*?)>").matcher(logcat);
+            if (matcher.find()) {
+                playManagedModules = ImmutableSet.copyOf(matcher.group(1).split(","));
+            } else {
+                playManagedModules = ImmutableSet.of();
+            }
+        }
+        return playManagedModules;
+    }
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/PocPusher.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/PocPusher.java
new file mode 100644
index 0000000..7f6dd2c
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/PocPusher.java
@@ -0,0 +1,141 @@
+/*
+ * 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.sts.common;
+
+import org.junit.runner.Description;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.junit.runner.Description;
+import org.junit.rules.TestWatcher;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.IAbi;
+
+import static org.junit.Assume.*;
+import static org.junit.Assert.*;
+
+public class PocPusher extends TestWatcher {
+    private ITestDevice device = null;
+    private CompatibilityBuildHelper buildHelper = null;
+    private IAbi abi = null;
+
+    private Set<String> filesToCleanup = new HashSet();
+    public boolean bitness32 = true;
+    public boolean bitness64 = true;
+    public boolean appendBitness = true;
+    public boolean cleanup = true;
+
+    @Override
+    protected void starting(Description d) {
+        bothBitness();
+        appendBitness = true;
+        cleanup = true;
+    }
+
+    @Override
+    protected void finished(Description d) {
+        for (Iterator<String> it = filesToCleanup.iterator(); it.hasNext(); ) {
+            String file = it.next();
+            try {
+                CLog.i("Cleaning up %s", file);
+                device.deleteFile(file);
+            } catch (DeviceNotAvailableException e) {
+                CLog.e("Device unavailable when cleaning up %s", file);
+                continue; // try to remove next time
+            }
+            it.remove();
+        }
+    }
+
+    public PocPusher setDevice(ITestDevice device) {
+        this.device = device;
+        return this;
+    }
+
+    public PocPusher setAbi(IAbi abi) {
+        this.abi = abi;
+        return this;
+    }
+
+    public PocPusher setBuild(IBuildInfo buildInfo) {
+        buildHelper = new CompatibilityBuildHelper(buildInfo);
+        return this;
+    }
+
+    public PocPusher appendBitness(boolean append) {
+        this.appendBitness = append;
+        return this;
+    }
+
+    public PocPusher cleanup(boolean cleanup) {
+        this.cleanup = cleanup;
+        return this;
+    }
+
+    public PocPusher only32() {
+        bitness32 = true;
+        bitness64 = false;
+        return this;
+    }
+
+    public PocPusher only64() {
+        bitness32 = false;
+        bitness64 = true;
+        return this;
+    }
+
+    public PocPusher bothBitness() {
+        bitness32 = true;
+        bitness64 = true;
+        return this;
+    }
+
+    public void pushFile(String testFile, String remoteFile)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        if (appendBitness) {
+            // if neither 32 or 64, nothing would ever be pushed.
+            assertTrue("bitness must be 32, 64, or both.", bitness32 || bitness64);
+
+            String bitness = abi.getBitness().trim();
+
+            // 32-bit doesn't have a 64-bit compatibility layer; skipping.
+            assumeFalse(bitness.equals("32") && !bitness32);
+
+            // push the 32-bit file on 64-bit device if a 64-bit file doesn't exist.
+            if (bitness.equals("64") && !bitness64) {
+                bitness = "32";
+                CLog.i("Pushing a 32-bit file onto a 64-bit device.");
+            }
+            testFile += bitness;
+        }
+        CLog.i("Pushing local: %s to remote: %s", testFile.toString(), remoteFile);
+        File localFile = buildHelper.getTestFile(testFile);
+        device.pushFile(localFile, remoteFile);
+        if (cleanup) {
+            filesToCleanup.add(remoteFile);
+        }
+    }
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/RegexUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/RegexUtils.java
new file mode 100644
index 0000000..f769c0c
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/RegexUtils.java
@@ -0,0 +1,154 @@
+/*
+ * 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.sts.common;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import static org.junit.Assert.*;
+
+public class RegexUtils {
+    private static final int TIMEOUT_DURATION = 20 * 60_000; // 20 minutes
+    private static final int WARNING_THRESHOLD = 1000; // 1 second
+    private static final int CONTEXT_RANGE = 100; // chars before/after matched input string
+
+    public static void assertContains(String pattern, String input) throws Exception {
+        assertFind(pattern, input, false, false);
+    }
+
+    public static void assertContainsMultiline(String pattern, String input) throws Exception {
+        assertFind(pattern, input, false, true);
+    }
+
+    public static void assertNotContains(String pattern, String input) throws Exception {
+        assertFind(pattern, input, true, false);
+    }
+
+    public static void assertNotContainsMultiline(String pattern, String input) throws Exception {
+        assertFind(pattern, input, true, true);
+    }
+
+    private static void assertFind(
+            String pattern, String input, boolean shouldFind, boolean multiline) {
+        // The input string throws an error when used after the timeout
+        TimeoutCharSequence timedInput = new TimeoutCharSequence(input, TIMEOUT_DURATION);
+        Matcher matcher = null;
+        if (multiline) {
+            // DOTALL lets .* match line separators
+            // MULTILINE lets ^ and $ match line separators instead of input start and end
+            matcher =
+                    Pattern.compile(pattern, Pattern.DOTALL | Pattern.MULTILINE)
+                            .matcher(timedInput);
+        } else {
+            matcher = Pattern.compile(pattern).matcher(timedInput);
+        }
+
+        try {
+            long start = System.currentTimeMillis();
+            boolean found = matcher.find();
+            long duration = System.currentTimeMillis() - start;
+
+            if (duration > WARNING_THRESHOLD) {
+                // Provide a warning to the test developer that their regex should be optimized.
+                CLog.logAndDisplay(LogLevel.WARN, "regex match took " + duration + "ms.");
+            }
+
+            if (found && shouldFind) { // failed notContains
+                String substring = input.substring(matcher.start(), matcher.end());
+                String context =
+                        getInputContext(
+                                input,
+                                matcher.start(),
+                                matcher.end(),
+                                CONTEXT_RANGE,
+                                CONTEXT_RANGE);
+                fail(
+                        "Pattern found: '"
+                                + pattern
+                                + "' -> '"
+                                + substring
+                                + "' for input:\n..."
+                                + context
+                                + "...");
+            } else if (!found && !shouldFind) { // failed contains
+                fail("Pattern not found: '" + pattern + "' for input:\n..." + input + "...");
+            }
+        } catch (TimeoutCharSequence.CharSequenceTimeoutException e) {
+            // regex match has taken longer than the timeout
+            // this usually means the input is extremely long or the regex is catastrophic
+            fail("Regex timeout with pattern: '" + pattern + "' for input:\n..." + input + "...");
+        }
+    }
+
+    /*
+     * Helper method to grab the nearby chars for a subsequence. Similar to the -A and -B flags for
+     * grep.
+     */
+    private static String getInputContext(String input, int start, int end, int before, int after) {
+        start = Math.max(0, start - before);
+        end = Math.min(input.length(), end + after);
+        return input.substring(start, end);
+    }
+
+    /*
+     * Wrapper for a given CharSequence. When charAt() is called, the current time is compared
+     * against the timeout. If the current time is greater than the expiration time, an exception is
+     * thrown. The expiration time is (time of object construction) + (timeout in milliseconds).
+     */
+    private static class TimeoutCharSequence implements CharSequence {
+        long expireTime = 0;
+        CharSequence chars = null;
+
+        TimeoutCharSequence(CharSequence chars, long timeout) {
+            this.chars = chars;
+            expireTime = System.currentTimeMillis() + timeout;
+        }
+
+        @Override
+        public char charAt(int index) {
+            if (System.currentTimeMillis() > expireTime) {
+                throw new CharSequenceTimeoutException(
+                        "TimeoutCharSequence was used after the expiration time.");
+            }
+            return chars.charAt(index);
+        }
+
+        @Override
+        public int length() {
+            return chars.length();
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            return new TimeoutCharSequence(
+                    chars.subSequence(start, end), expireTime - System.currentTimeMillis());
+        }
+
+        @Override
+        public String toString() {
+            return chars.toString();
+        }
+
+        private static class CharSequenceTimeoutException extends RuntimeException {
+            public CharSequenceTimeoutException(String message) {
+                super(message);
+            }
+        }
+    }
+}