Add a CTS test for dumpsys meminfo --unreachable

Add a basic test that ensures dumpsys meminfo --unreachable returns
something reasonable looking.

Bug: 285967712
Test: CtsMemunreachableTestCases
Change-Id: Ib56edeb7775788a06f4ff7c18c26786c735c6176
diff --git a/hostsidetests/memunreachable/Android.bp b/hostsidetests/memunreachable/Android.bp
new file mode 100644
index 0000000..1eb1f50
--- /dev/null
+++ b/hostsidetests/memunreachable/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsMemunreachableTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "truth-host-prebuilt",
+    ],
+    static_libs: [
+        "compatibility-host-util-axt",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    data: [
+        ":CtsMemunreachableDebuggableTestApp",
+        ":CtsMemunreachableUndebuggableTestApp",
+    ],
+    per_testcase_directory: true,
+}
diff --git a/hostsidetests/memunreachable/AndroidTest.xml b/hostsidetests/memunreachable/AndroidTest.xml
new file mode 100644
index 0000000..94c43ef
--- /dev/null
+++ b/hostsidetests/memunreachable/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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="Config for the CTS libmemunreachable test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMemunreachableDebuggableTestApp.apk" />
+        <option name="test-file-name" value="CtsMemunreachableUndebuggableTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsMemunreachableTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/memunreachable/OWNERS b/hostsidetests/memunreachable/OWNERS
new file mode 100644
index 0000000..8976398
--- /dev/null
+++ b/hostsidetests/memunreachable/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 119452
+ccross@android.com
+cferris@google.com
\ No newline at end of file
diff --git a/hostsidetests/memunreachable/TEST_MAPPING b/hostsidetests/memunreachable/TEST_MAPPING
new file mode 100644
index 0000000..049cf0e
--- /dev/null
+++ b/hostsidetests/memunreachable/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMemunreachableTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/memunreachable/src/android/memunreachable/cts/MemunreachableTests.kt b/hostsidetests/memunreachable/src/android/memunreachable/cts/MemunreachableTests.kt
new file mode 100644
index 0000000..fff99ac3
--- /dev/null
+++ b/hostsidetests/memunreachable/src/android/memunreachable/cts/MemunreachableTests.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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 android.memunreachable.cts
+
+import com.android.tradefed.device.DeviceNotAvailableException
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Runs the host side tests for Environment.java
+ */
+@RunWith(DeviceJUnit4ClassRunner::class)
+class MemunreachableTests : BaseHostJUnit4Test() {
+    @Throws(DeviceNotAvailableException::class)
+    private fun dumpsysMemunreachable(pkg: String, activity: String): String? {
+        device.executeShellCommand("am start -W -n $pkg/$activity")
+        val meminfo = device.executeShellCommand("dumpsys meminfo --unreachable $pkg")
+        val matcher = UNREACHABLE_MEMORY_PATTERN.find(meminfo)
+        return matcher?.groupValues?.get(1)
+    }
+
+    @Test
+    @Throws(DeviceNotAvailableException::class)
+    fun testDumpsysMeminfoUnreachableDebuggableApp() {
+        // Test that an app marked android:debuggable can have dumpsys meminfo --unreachable run on it.
+        val meminfo = dumpsysMemunreachable(DEBUGGABLE_TEST_PACKAGE, TEST_ACTIVITY)
+        Truth.assertThat(meminfo).isNotNull()
+        Truth.assertThat(meminfo).startsWith("Unreachable memory\n")
+        Truth.assertThat(meminfo).containsMatch(UNREACHABLE_MEMORY_RESULT_PATTERN.toPattern())
+    }
+
+    @Test
+    @Throws(DeviceNotAvailableException::class)
+    fun testDumpsysMeminfoUnreachableUndebuggableApp() {
+        // Test that an app not marked android:debuggable can have dumpsys meminfo --unreachable run on it
+        // on a userdebug build but not on a user build.
+        val meminfo = dumpsysMemunreachable(UNDEBUGGABLE_TEST_PACKAGE, TEST_ACTIVITY)
+        Truth.assertThat(meminfo).startsWith("Unreachable memory\n")
+        if (device.getProperty("ro.build.type") == "user") {
+            Truth.assertWithMessage("on a user build").that(meminfo)
+                    .doesNotContainMatch(UNREACHABLE_MEMORY_RESULT_PATTERN.toPattern())
+        } else {
+            Truth.assertWithMessage("on userdebug or eng build").that(meminfo)
+                    .containsMatch(UNREACHABLE_MEMORY_RESULT_PATTERN.toPattern())
+        }
+    }
+
+    companion object {
+        private const val DEBUGGABLE_TEST_PACKAGE = "android.memunreachable.app.debuggable"
+        private const val UNDEBUGGABLE_TEST_PACKAGE = "android.memunreachable.app.undebuggable"
+        private const val TEST_ACTIVITY = "android.memunreachable.app.TestMemunreachableActivity"
+        private val UNREACHABLE_MEMORY_PATTERN = Regex("\n\\s*(Unreachable memory\n.*)")
+        private val UNREACHABLE_MEMORY_RESULT_PATTERN =
+                Regex("\\d+ bytes in \\d+ unreachable allocations")
+    }
+}
diff --git a/hostsidetests/memunreachable/test-app/Android.bp b/hostsidetests/memunreachable/test-app/Android.bp
new file mode 100644
index 0000000..8a31bd5
--- /dev/null
+++ b/hostsidetests/memunreachable/test-app/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsMemunreachableDebuggableTestApp",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.kt"],
+    manifest: "AndroidManifest-debuggable.xml",
+    sdk_version: "current",
+    optimize: {
+        enabled: true,
+        shrink: true,
+    },
+}
+
+android_test_helper_app {
+    name: "CtsMemunreachableUndebuggableTestApp",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.kt"],
+    manifest: "AndroidManifest-undebuggable.xml",
+    sdk_version: "current",
+    optimize: {
+        enabled: true,
+        shrink: true,
+    },
+}
diff --git a/hostsidetests/memunreachable/test-app/AndroidManifest-debuggable.xml b/hostsidetests/memunreachable/test-app/AndroidManifest-debuggable.xml
new file mode 100755
index 0000000..4dc1573
--- /dev/null
+++ b/hostsidetests/memunreachable/test-app/AndroidManifest-debuggable.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 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="android.memunreachable.app.debuggable"
+    android:targetSandboxVersion="2">
+
+    <application android:debuggable="true" >
+        <activity android:name="android.memunreachable.app.TestMemunreachableActivity"
+                android:exported="true" />
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/memunreachable/test-app/AndroidManifest-undebuggable.xml b/hostsidetests/memunreachable/test-app/AndroidManifest-undebuggable.xml
new file mode 100755
index 0000000..7dad85d
--- /dev/null
+++ b/hostsidetests/memunreachable/test-app/AndroidManifest-undebuggable.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 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="android.memunreachable.app.undebuggable"
+    android:targetSandboxVersion="2">
+
+    <application>
+        <activity android:name="android.memunreachable.app.TestMemunreachableActivity"
+                android:exported="true" />
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/memunreachable/test-app/src/android/memunreachable/app/TestMemunreachableActivity.kt b/hostsidetests/memunreachable/test-app/src/android/memunreachable/app/TestMemunreachableActivity.kt
new file mode 100644
index 0000000..1362aa8
--- /dev/null
+++ b/hostsidetests/memunreachable/test-app/src/android/memunreachable/app/TestMemunreachableActivity.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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 android.memunreachable.app
+
+/**
+ * A simple activity to be queried for memory leaks.
+ */
+class TestMemunreachableActivity : android.app.Activity() {
+    private val TAG: kotlin.String? = TestMemunreachableActivity::class.java.getSimpleName()
+}