Init tests for android_local_test rule. Part of #106

PiperOrigin-RevId: 571194386
Change-Id: Ic58b770811f2ddb62ff9f61fb5109d617db8eda1
diff --git a/MODULE.bazel b/MODULE.bazel
index 39c7032..2b38cc9 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -13,7 +13,7 @@
 bazel_dep(name = "protobuf", version = "3.19.0", repo_name = "com_google_protobuf")
 bazel_dep(name = "rules_jvm_external", version = "4.5")
 bazel_dep(name = "bazel_skylib", version = "1.0.3")
-bazel_dep(name = "rules_robolectric", version = "4.10")
+bazel_dep(name = "rules_robolectric", version = "4.10", repo_name = "robolectric")
 
 register_toolchains("//toolchains/android:all")
 
diff --git a/prereqs.bzl b/prereqs.bzl
index 627ccad..893fb43 100644
--- a/prereqs.bzl
+++ b/prereqs.bzl
@@ -87,9 +87,9 @@
     maybe(
         http_archive,
         name = "robolectric",
-        urls = ["https://github.com/robolectric/robolectric-bazel/archive/4.9.2.tar.gz"],
-        strip_prefix = "robolectric-bazel-4.9.2",
-        sha256 = "7e007fcfdca7b7228cb4de72707e8b317026ea95000f963e91d5ae365be52d0d",
+        urls = ["https://github.com/robolectric/robolectric-bazel/archive/4.10.3.tar.gz"],
+        strip_prefix = "robolectric-bazel-4.10.3",
+        sha256 = "1b199a932cbde4af728dd8275937091adbb89a4bf63d326de49e6d0a42e723bf",
     )
 
     maybe(
diff --git a/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD b/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
index 16790f0..a164ae6 100644
--- a/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
+++ b/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
@@ -4,6 +4,14 @@
     android_binary(
         name = name,
         srcs = ["BasicActivity.java"],
+        # Work around --java_runtime_version=17 and --java_language_version=11
+        # set in the presubmit tests.
+        javacopts = [
+            "-target",
+            "8",
+            "-source",
+            "8",
+        ],
         manifest = "AndroidManifest.xml",
         proguard_specs = specs,
         resource_files = glob(["res/**"]),
@@ -13,9 +21,6 @@
             ":basic_lib",
             ":lib_with_specs",
         ],
-        # Work around --java_runtime_version=17 and --java_language_version=11
-        # set in the presubmit tests.
-        javacopts = ["-target", "8", "-source", "8"],
     )
     for name, specs, shrink in [
         (
diff --git a/test/rules/android_local_test/BUILD b/test/rules/android_local_test/BUILD
new file mode 100644
index 0000000..590a523
--- /dev/null
+++ b/test/rules/android_local_test/BUILD
@@ -0,0 +1,69 @@
+load("//rules:rules.bzl", "android_local_test")
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load(":java_launcher_integration_test.bzl", "android_local_test_launcher_integration_test_suite")
+load(":java_launcher_test.bzl", "android_local_test_launcher_test_suite")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+exports_files([
+    "EmptyTest.java",
+    "integration_test_stub_script.sh",
+])
+
+bzl_library(
+    name = "bzl",
+    srcs = glob(["*.bzl"]),
+    visibility = ["//visibility:private"],
+)
+
+android_local_test(
+    name = "sample_test_default_launcher",
+    srcs = ["EmptyTest.java"],
+    custom_package = "com.google.android.emptytest",
+    test_class = "com.google.android.emptytest.EmptyTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "sample_test_default_launcher_integration",
+    srcs = ["EmptyTest.java"],
+    custom_package = "com.google.android.emptytest",
+    test_class = "com.google.android.emptytest.EmptyTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+    ],
+)
+
+config_setting(
+    name = "jdk17",
+    values = {
+        "java_runtime_version": "17",
+    },
+)
+
+android_local_test_launcher_test_suite(
+    name = "android_local_test_launcher_tests",
+    expected_executable = select({
+        ":jdk17": "../remotejdk17_linux/bin/java",
+        "//conditions:default": "third_party/java/jdk/jdk-sts-k8/bin/java",
+    }),
+)
+
+android_local_test_launcher_integration_test_suite(
+    name = "android_local_test_launcher_integration_tests",
+    expected_executable = select({
+        ":jdk17": "rules_android/../remotejdk17_linux/bin/java",
+        "//conditions:default": "rules_android/third_party/java/jdk/jdk-sts-k8/bin/java",
+    }),
+)
diff --git a/test/rules/android_local_test/EmptyTest.java b/test/rules/android_local_test/EmptyTest.java
new file mode 100644
index 0000000..8f85825
--- /dev/null
+++ b/test/rules/android_local_test/EmptyTest.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.google.android.emptytest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class EmptyTest {
+  @Test
+  public void emptyTest() {
+  }
+}
diff --git a/test/rules/android_local_test/integration_test_stub_script.sh b/test/rules/android_local_test/integration_test_stub_script.sh
new file mode 100644
index 0000000..9f836f2
--- /dev/null
+++ b/test/rules/android_local_test/integration_test_stub_script.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+exit_with_error() {
+  echo "Expected to find JAVABIN=\${JAVABIN:-\${JAVA_RUNFILES}/%expected_executable% but was"
+  echo $reference
+  exit 1
+}
+
+executable="%executable%"
+reference=`grep -o 'JAVABIN=\\${JAVABIN:-\\${JAVA_RUNFILES}/.*}' $executable`
+grep -c "JAVABIN:-\${JAVA_RUNFILES}/%expected_executable%" $executable >> /dev/null || exit_with_error
diff --git a/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml b/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml
new file mode 100644
index 0000000..978e2c8
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.starlark_resources">
+  <uses-sdk
+      android:minSdkVersion="21"
+      android:targetSdkVersion="24" />
+</manifest>
diff --git a/test/rules/android_local_test/java/com/starlark_resources/BUILD b/test/rules/android_local_test/java/com/starlark_resources/BUILD
new file mode 100644
index 0000000..f5824cd
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/BUILD
@@ -0,0 +1,177 @@
+# Tests that run on head android_local_test rule to verify Starlark resource processing pipeline.
+
+load(
+    "//rules:rules.bzl",
+    "android_library",
+    "android_local_test",
+)
+load(
+    "//test/rules/android_local_test:test.bzl",
+    "rule_test",
+)
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"])
+
+android_local_test(
+    name = "no_deps_with_resources",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "no_deps_with_resources_rule_test",
+    target_under_test = ":no_deps_with_resources",
+)
+
+# TODO(b/161179595): Add test to exercise resource_configuration_filter wiring.
+
+# TODO(aarmin): Add test to exercise densities and manifest_values wiring.
+
+android_library(
+    name = "resource_processing",
+    assets = ["assets/bar.txt"],
+    assets_dir = "assets",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+)
+
+android_local_test(
+    name = "single_resource_dep_without_manifest",
+    srcs = ["SampleTest.java"],
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "single_resource_dep_without_manifest_rule_test",
+    target_under_test = ":single_resource_dep_without_manifest",
+)
+
+android_local_test(
+    name = "single_resource_dep",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "single_resource_dep_rule_test",
+    target_under_test = ":single_resource_dep",
+)
+
+android_library(
+    name = "resources_with_dep_with_res",
+    assets = ["assets/foo.txt"],
+    assets_dir = "assets",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["another_res/**"]),
+    deps = [":resource_processing"],
+)
+
+android_local_test(
+    name = "multiple_resource_deps",
+    srcs = ["SampleTestMultipleDeps.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTestMultipleDeps",
+    deps = [
+        ":resources_with_dep_with_res",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "multiple_resource_deps_rule_test",
+    target_under_test = ":multiple_resource_deps",
+)
+
+android_library(
+    name = "resource_processing_with_neverlink",
+    manifest = "AndroidManifest.xml",
+    neverlink = True,
+    resource_files = glob(["res/**"]),
+)
+
+android_local_test(
+    name = "depends_on_neverlink_lib",
+    srcs = ["SampleTestNeverlinkDep.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTestNeverlinkDep",
+    deps = [
+        ":resource_processing_with_neverlink",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "depends_on_neverlink_lib_rule_test",
+    expect_resources = False,
+    target_under_test = ":depends_on_neverlink_lib",
+)
+
+android_local_test(
+    name = "manifest_values_low_minsdk",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {"minSdkVersion": "15"},
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+android_local_test(
+    name = "manifest_values_low_minsdk_no_manifest",
+    srcs = ["SampleTest.java"],
+    manifest_values = {"minSdkVersion": "15"},
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java
new file mode 100644
index 0000000..32529df
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTest {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java
new file mode 100644
index 0000000..7091594
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTestMultipleDeps {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+    assertEquals(
+        "Check resource `another_res`", "Another Res!", resources.getString(R.string.another_res));
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java
new file mode 100644
index 0000000..3da5dca
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTestNeverlinkDep {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    try {
+      // This line should throw an exception since there are no resources inherited from
+      // neverlink deps.
+      assertEquals(
+          "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+    } catch(NoClassDefFoundError e) {
+      return;
+    }
+    fail("Expected NoClassDefFoundError exception");
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml b/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml
new file mode 100644
index 0000000..554ea5e
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="another_res">Another Res!</string>
+</resources>
diff --git a/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt b/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt
@@ -0,0 +1 @@
+bar
diff --git a/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt b/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt
@@ -0,0 +1 @@
+foo
diff --git a/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml b/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml
new file mode 100644
index 0000000..2686d09
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="a_string">Hello World!</string>
+</resources>
diff --git a/test/rules/android_local_test/java_launcher_integration_test.bzl b/test/rules/android_local_test/java_launcher_integration_test.bzl
new file mode 100644
index 0000000..a09a566
--- /dev/null
+++ b/test/rules/android_local_test/java_launcher_integration_test.bzl
@@ -0,0 +1,64 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+""" Bazel rules that test the Android Local Test rule.
+
+launcher_test: Asserts that the executable is populated correctly in the target script.
+"""
+
+def _android_local_test_launcher_integration(ctx):
+    substitutions = {
+        "%executable%": ctx.attr.target[DefaultInfo].files_to_run.executable.short_path,
+        "%expected_executable%": ctx.attr.expected_executable,
+    }
+    runner = ctx.actions.declare_file(ctx.label.name + "_runner.sh")
+    ctx.actions.expand_template(
+        template = ctx.file._test_stub_script,
+        substitutions = substitutions,
+        output = runner,
+    )
+    return [
+        DefaultInfo(
+            executable = runner,
+            runfiles = ctx.runfiles(
+                files = [ctx.attr.target[DefaultInfo].files_to_run.executable],
+            ),
+        ),
+    ]
+
+integration_test = rule(
+    attrs = dict(
+        target = attr.label(),
+        _test_stub_script = attr.label(
+            cfg = "exec",
+            default = ":integration_test_stub_script.sh",
+            allow_single_file = True,
+        ),
+        expected_executable = attr.string(),
+    ),
+    test = True,
+    implementation = _android_local_test_launcher_integration,
+)
+
+def android_local_test_launcher_integration_test_suite(name, expected_executable):
+    integration_test(
+        name = "android_local_test_default_launcher_integration",
+        target = ":sample_test_default_launcher_integration",
+        expected_executable = expected_executable,
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [":android_local_test_default_launcher_integration"],
+    )
diff --git a/test/rules/android_local_test/java_launcher_test.bzl b/test/rules/android_local_test/java_launcher_test.bzl
new file mode 100644
index 0000000..36d90fe
--- /dev/null
+++ b/test/rules/android_local_test/java_launcher_test.bzl
@@ -0,0 +1,50 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+""" Bazel rules that test the Android Local Test rule.
+
+launcher_test: Asserts that the executable is contained in the target's runfiles.
+"""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("@bazel_skylib//lib:sets.bzl", "sets")
+
+def _android_local_test_default_launcher(ctx):
+    env = analysistest.begin(ctx)
+    target_under_test = analysistest.target_under_test(env)
+    expected_runfile = getattr(env.ctx.attr, "expected_runfile")
+
+    runfiles = sets.make([f.short_path for f in target_under_test[DefaultInfo].default_runfiles.files.to_list()])
+    asserts.true(env, sets.contains(runfiles, expected_runfile), "Expect runfiles to contains {0}".format(expected_runfile))
+
+    return analysistest.end(env)
+
+android_local_test_default_launcher_test = analysistest.make(
+    _android_local_test_default_launcher,
+    attrs = {
+        "expected_runfile": attr.string(),
+    },
+)
+
+def android_local_test_launcher_test_suite(name, expected_executable):
+    android_local_test_default_launcher_test(
+        name = "android_local_test_default_launcher",
+        target_under_test = ":sample_test_default_launcher",
+        expected_runfile = expected_executable,
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [":android_local_test_default_launcher"],
+    )
diff --git a/test/rules/android_local_test/non_java/AndroidManifest.xml b/test/rules/android_local_test/non_java/AndroidManifest.xml
new file mode 100644
index 0000000..978e2c8
--- /dev/null
+++ b/test/rules/android_local_test/non_java/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.starlark_resources">
+  <uses-sdk
+      android:minSdkVersion="21"
+      android:targetSdkVersion="24" />
+</manifest>
diff --git a/test/rules/android_local_test/non_java/BUILD b/test/rules/android_local_test/non_java/BUILD
new file mode 100644
index 0000000..e3f10b7
--- /dev/null
+++ b/test/rules/android_local_test/non_java/BUILD
@@ -0,0 +1,51 @@
+# Tests that run on head android_local_test rule to verify Starlark resource processing pipeline.
+
+load(
+    "//rules:rules.bzl",
+    "android_library",
+    "android_local_test",
+)
+load(
+    "//test/rules/android_local_test:test.bzl",
+    "rule_test",
+)
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"])
+
+android_library(
+    name = "resource_processing",
+    assets = ["assets/bar.txt"],
+    assets_dir = "assets",
+    custom_package = "com.starlark_resources",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+)
+
+# A custom package is necessary when an android_local_test is under a non-java directory.
+android_local_test(
+    name = "with_custom_package",
+    srcs = ["SampleTest.java"],
+    custom_package = "com.starlark_resources",
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "with_custom_package_rule_test",
+    target_under_test = ":with_custom_package",
+)
+
+# TODO(b/161359429): Create failure test for missing custom package under a non-java directory.
diff --git a/test/rules/android_local_test/non_java/SampleTest.java b/test/rules/android_local_test/non_java/SampleTest.java
new file mode 100644
index 0000000..32529df
--- /dev/null
+++ b/test/rules/android_local_test/non_java/SampleTest.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTest {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+  }
+}
diff --git a/test/rules/android_local_test/non_java/assets/bar.txt b/test/rules/android_local_test/non_java/assets/bar.txt
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/test/rules/android_local_test/non_java/assets/bar.txt
@@ -0,0 +1 @@
+bar
diff --git a/test/rules/android_local_test/non_java/res/values/strings.xml b/test/rules/android_local_test/non_java/res/values/strings.xml
new file mode 100644
index 0000000..2686d09
--- /dev/null
+++ b/test/rules/android_local_test/non_java/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="a_string">Hello World!</string>
+</resources>
diff --git a/test/rules/android_local_test/test.bzl b/test/rules/android_local_test/test.bzl
new file mode 100644
index 0000000..5b1677e
--- /dev/null
+++ b/test/rules/android_local_test/test.bzl
@@ -0,0 +1,117 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Bazel rules that test the Android Local Test rule.
+
+rule_test: Inspect and assert on rule providers.
+"""
+
+load("//rules:providers.bzl", "AndroidFilteredJdepsInfo")
+load("//test/utils:asserts.bzl", "asserts")
+
+VALIDATION = "_validation"
+
+def _rule_test_impl(ctx):
+    # Assert on expected providers
+    if not JavaInfo in ctx.attr.target_under_test:
+        fail("Missing JavaInfo provider")
+    if not AndroidFilteredJdepsInfo in ctx.attr.target_under_test:
+        fail("Missing AndroidFilteredJdepsInfo provider")
+
+    # Collecting validation outputs from deps
+    transitive_validation_outputs = []
+    for dep in ctx.attr.deps:
+        if hasattr(dep[OutputGroupInfo], VALIDATION):
+            transitive_validation_outputs.append(dep[OutputGroupInfo]._validation)
+
+    output_group_info = dict(ctx.attr.expected_output_group_info)
+    if VALIDATION in output_group_info:
+        output_group_info[VALIDATION] = (
+            output_group_info[VALIDATION] +
+            [f.basename for f in depset(transitive = transitive_validation_outputs).to_list()]
+        )
+    asserts.provider.output_group_info(
+        output_group_info,
+        ctx.attr.target_under_test[OutputGroupInfo],
+    )
+
+    # Create test script to assert on provider contents
+    args = dict(
+        jdeps_print_tool = ctx.executable._jdeps_print_tool.short_path,
+        jdeps = ctx.attr.target_under_test[JavaInfo].outputs.jdeps.short_path,
+        filtered_jdeps = ctx.attr.target_under_test[AndroidFilteredJdepsInfo].jdeps.short_path,
+        res_jar = ctx.attr.target_under_test.label.name + "_resources.jar",
+        expect_resources = str(ctx.attr.expect_resources),
+    )
+    test_script = ctx.actions.declare_file("%s_script.sh" % ctx.label.name)
+    ctx.actions.write(
+        test_script,
+        """
+jdeps=`{jdeps_print_tool} --in {jdeps} | sed 's#_migrated/##g'`
+filtered_jdeps=`{jdeps_print_tool} --in {filtered_jdeps} | sed 's#_migrated/##g'`
+
+entries=`echo "$jdeps" | wc -l`
+matches=`echo "$jdeps" | grep '{res_jar}' | wc -l`
+filtered_entries=`echo "$filtered_jdeps" | wc -l`
+filtered_matches=`echo "$filtered_jdeps" | grep '{res_jar}' | wc -l`
+
+expected_matches=1
+expected_filtering_differences=1
+if [ {expect_resources} == "False" ]; then
+  expected_matches=0
+  expected_filtering_differences=0
+fi
+
+if [ $matches -ne $expected_matches ]; then
+  echo "Expected one resource.jar in jdeps"
+  exit 1
+elif [ $filtered_matches -ne 0 ]; then
+  echo "Expected no resource.jar in filtered jdeps"
+  exit 1
+elif [ $(($entries-$filtered_entries)) -ne $expected_filtering_differences ]; then
+  echo "Expected to remove one item when filtering"
+  exit 1
+fi
+""".format(**args),
+        is_executable = True,
+    )
+    return [
+        DefaultInfo(
+            runfiles = ctx.runfiles(
+                files = [
+                    test_script,
+                    ctx.executable._jdeps_print_tool,
+                    ctx.attr.target_under_test[JavaInfo].outputs.jdeps,
+                    ctx.attr.target_under_test[AndroidFilteredJdepsInfo].jdeps,
+                ],
+            ),
+            executable = test_script,
+        ),
+    ]
+
+rule_test = rule(
+    attrs = dict(
+        asserts.provider.attrs.items(),
+        expect_resources = attr.bool(default = True),
+        target_under_test = attr.label(),
+        deps = attr.label_list(),
+        _jdeps_print_tool = attr.label(
+            cfg = "exec",
+            default = "//src/tools/jdeps:print_jdeps",
+            executable = True,
+        ),
+    ),
+    implementation = _rule_test_impl,
+    test = True,
+)