diff --git a/.github/workflows/ci.bazelrc b/.github/workflows/ci.bazelrc
index 55b2b35..fe8a059 100644
--- a/.github/workflows/ci.bazelrc
+++ b/.github/workflows/ci.bazelrc
@@ -4,6 +4,13 @@ common --experimental_google_legacy_api
build --verbose_failures
build --worker_verbose
+# Required by @rules_android
+build --experimental_enable_android_migration_apis
+build --incompatible_java_common_parameters
+build --android_databinding_use_v3_4_args
+build --experimental_android_databinding_v2
+
# Don't rely on test logs being easily accessible from the test runner,
# though it makes the log noisier.
test --test_output=errors
+test --sandbox_debug
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8f1ca5b..9c210b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,3 +64,56 @@ jobs:
- name: "Running unit tests"
run: |
bazel --bazelrc=${{ github.workspace }}/.github/workflows/ci.bazelrc test --enable_bzlmod=${{ matrix.bzlmod }} //test/...
+
+ instrumentation-tests:
+ strategy:
+ # Allow tests to continue on other devices if they fail on one device.
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ api-level: [29]
+ bzlmod: [false]
+ runs-on: ${{ matrix.os }}
+ needs: build
+ timeout-minutes: 60
+ steps:
+ - name: "Checkout the sources"
+ uses: actions/checkout@v4
+
+ - name: "Enable KVM"
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ - name: "Init AVD cache"
+ uses: actions/cache@v3
+ id: avd-cache
+ with:
+ path: |
+ ~/.android/avd/*
+ ~/.android/adb*
+ key: avd-${{ matrix.api-level }}
+
+ - name: "Create AVD and generate snapshot for caching"
+ if: steps.avd-cache.outputs.cache-hit != 'true'
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: ${{ matrix.api-level }}
+ force-avd-creation: false
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: false
+ script: echo "Generated AVD snapshot for caching."
+
+ - name: "Run instrumentation test"
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: ${{ matrix.api-level }}
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ avd-name: android_avd
+ disable-animations: true
+ working-directory: ./example/ait
+ script: |
+ $ANDROID_HOME/platform-tools/adb get-serialno
+ bazel --bazelrc=${{ github.workspace }}/.github/workflows/ci.bazelrc test --enable_bzlmod=${{ matrix.bzlmod }} //:sample_instrumentation
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3819313
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.swp
+*.swo
diff --git a/MODULE.bazel b/MODULE.bazel
index 96c8fcb..db14b45 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -17,7 +17,7 @@ maven.install(
name = "maven",
# To generate the maven lockfile, run this command:
# bazel run --noenable_bzlmod @maven//:pin
- lock_file = "@//:maven_install.json",
+ lock_file = "//:maven_install.json",
repositories = [
"https://maven.google.com",
"https://repo1.maven.org/maven2",
@@ -33,8 +33,8 @@ use_repo(
RULES_ANDROID_COMMIT = "f49f2e3af10a32f813ca294a0f025fd27294861a"
RULES_ANDROID_SHA = "EGpnoftEKL6mc6QfSi7kAGqzhwtY8wsyBl5mAK84AQM="
archive_override(
- module_name = "rules_android",
- urls = ["https://github.com/bazelbuild/rules_android/archive/%s.zip" % RULES_ANDROID_COMMIT],
- integrity = "sha256-%s" % RULES_ANDROID_SHA,
- strip_prefix = "rules_android-%s" % RULES_ANDROID_COMMIT
+ module_name = "rules_android",
+ urls = ["https://github.com/bazelbuild/rules_android/archive/%s.zip" % RULES_ANDROID_COMMIT],
+ integrity = "sha256-%s" % RULES_ANDROID_SHA,
+ strip_prefix = "rules_android-%s" % RULES_ANDROID_COMMIT
)
diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod
index 3aa901a..c1076c1 100644
--- a/WORKSPACE.bzlmod
+++ b/WORKSPACE.bzlmod
@@ -1,8 +1,9 @@
workspace(name = "rules_utp")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("@rules_android//rules:rules.bzl", "android_sdk_repository")
maybe(
android_sdk_repository,
name = "androidsdk",
-)
\ No newline at end of file
+)
diff --git a/defs.bzl b/defs.bzl
index d29b4b2..7e3c9c5 100644
--- a/defs.bzl
+++ b/defs.bzl
@@ -19,7 +19,7 @@ load("@rules_android//:defs.bzl", "rules_android_workspace")
load("@rules_jvm_external//:defs.bzl", "maven_install")
def rules_utp_workspace():
- """ Sets up workspace dependencies for rules_android."""
+ """ Sets up workspace dependencies for rules_utp."""
bazel_skylib_workspace()
UTP_VERSION = "0.0.9-alpha01"
diff --git a/example/ait/BUILD b/example/ait/BUILD
new file mode 100644
index 0000000..1a27b4d
--- /dev/null
+++ b/example/ait/BUILD
@@ -0,0 +1,109 @@
+load("@rules_android//rules:rules.bzl", "android_binary", "android_library")
+load(
+ "@rules_utp//launcher:rules.bzl",
+ "UTP_RELEASE",
+ "android_environment",
+ "environment",
+ "environment_variable",
+ "instrumentation",
+ "instrumentation_args",
+ "utp_entry_point",
+)
+load(":rule.bzl", "android_instrumentation_test")
+
+package(
+ default_applicable_licenses = ["@rules_utp//:license"],
+ default_visibility = ["//visibility:public"],
+)
+
+android_instrumentation_test(
+ name = "sample_instrumentation",
+ device_serial = "emulator-5554", # Default serial-no
+ entry_point = ":entry_point",
+ environment = ":default_environment",
+ instrumentation = ":device_instrumentation",
+ instrumentation_args = ":device_instrumentation_args",
+ support_apps = [":sample_app"],
+ test_app = ":test_app",
+ utp_release = "@rules_utp" + UTP_RELEASE,
+)
+
+android_binary(
+ name = "test_app",
+ instruments = ":sample_app",
+ manifest = "//lib:AndroidManifest.xml",
+ multidex = "native",
+ deps = [
+ ":test_src",
+ "@android_maven//:com_android_support_multidex",
+ ],
+)
+
+android_library(
+ name = "test_src",
+ srcs = ["//lib:SampleTest.java"],
+ deps = [
+ ":sample_lib",
+ "@androidx_maven//:androidx_test_core",
+ "@androidx_maven//:androidx_test_espresso_espresso_core",
+ "@androidx_maven//:androidx_test_ext_junit",
+ "@androidx_maven//:androidx_test_runner",
+ "@bazel_tools//tools/jdk:TestRunner",
+ ],
+)
+
+android_binary(
+ name = "sample_app",
+ custom_package = "com.sample",
+ manifest = "//lib:SampleAndroidManifest.xml",
+ multidex = "native",
+ deps = [
+ ":sample_lib",
+ "@android_maven//:com_android_support_multidex",
+ ],
+)
+
+android_library(
+ name = "sample_lib",
+ srcs = ["//lib:SampleActivity.java"],
+ custom_package = "com.sample",
+ manifest = "//lib:SampleAndroidManifest.xml",
+ resource_files = ["//lib:resource_files"],
+)
+
+## instruments
+
+instrumentation(
+ name = "device_instrumentation",
+ app_package = "com.sample",
+ test_package = "com.sample.test",
+ test_runner_class = "androidx.test.runner.AndroidJUnitRunner",
+)
+
+instrumentation_args(
+ name = "device_instrumentation_args",
+ # enable_debug = True, # Need to launch debugger otherwise test will hang
+)
+
+## environment
+
+android_environment(
+ name = "android_environment",
+ test_log_dir = "/tmp/ait/testlog",
+ test_run_log = "test-results.log",
+)
+
+environment(
+ name = "default_environment",
+ android_environment = ":android_environment",
+ output_dir = environment_variable("TEST_UNDECLARED_OUTPUTS_DIR"),
+ runfiles_dir = "{}/test_local_repo".format(environment_variable("TEST_SRCDIR")),
+ tmp_dir = environment_variable("TEST_TMPDIR"),
+)
+
+### UTP entry point
+utp_entry_point(
+ name = "entry_point",
+ testonly = True,
+ utp_release = "@rules_utp" + UTP_RELEASE,
+)
diff --git a/example/ait/MODULE.bazel b/example/ait/MODULE.bazel
new file mode 100644
index 0000000..0be7fad
--- /dev/null
+++ b/example/ait/MODULE.bazel
@@ -0,0 +1,68 @@
+module(
+ name = "example_ait",
+ version = "TODO",
+)
+
+bazel_dep(name = "rules_android", version = "0.1.1")
+bazel_dep(name = "rules_utp", version = "0.0.1")
+bazel_dep(name = "rules_jvm_external", version = "5.3")
+
+
+bazel_dep(name = "rules_java", version = "7.3.2")
+rules_java_toolchains = use_extension("@rules_java//java:extensions.bzl", "toolchains")
+use_repo(rules_java_toolchains, "remote_java_tools")
+bazel_dep(name = "bazel_skylib", version = "1.4.1")
+
+
+UTP_VERSION = "0.0.9-alpha01"
+SERVICES_VERSION = "1.4.2"
+androidxLibVersion = "1.0.0"
+coreVersion = "1.6.0-alpha05"
+extJUnitVersion = "1.2.0-alpha03"
+espressoVersion = "3.6.0-alpha03"
+runnerVersion = "1.6.0-alpha06"
+rulesVersion = "1.6.0-alpha03"
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+maven.install(
+ name = "android_maven",
+ artifacts = [
+ "com.android.support:multidex:1.0.3",
+ "com.android.support.test:runner:1.0.2",
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+)
+maven.install(
+ name = "androidx_maven",
+ artifacts = [
+ "androidx.test:core:" + coreVersion,
+ "androidx.test.espresso:espresso-core:" + espressoVersion,
+ "androidx.test.ext:junit:" + extJUnitVersion,
+ "androidx.test:runner:" + runnerVersion,
+ "androidx.test:rules:" + rulesVersion,
+ ],
+ fetch_sources = True,
+ # To generate the maven lockfile, run this command:
+ # bazel run --noenable_bzlmod @rules_androidx_maven//:pin
+ # lock_file = "//:rules_androidx_maven_install.json",
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+)
+use_repo(
+ maven,
+ "android_maven", "androidx_maven"
+)
+
+
+remote_android_extensions = use_extension("@bazel_tools//tools/android:android_extensions.bzl", "remote_android_tools_extensions")
+use_repo(remote_android_extensions, "android_gmaven_r8", "android_tools")
+
+local_path_override(
+ module_name = "rules_utp",
+ path = "../../",
+)
diff --git a/example/ait/WORKSPACE b/example/ait/WORKSPACE
new file mode 100644
index 0000000..edd81eb
--- /dev/null
+++ b/example/ait/WORKSPACE
@@ -0,0 +1,29 @@
+workspace(name = "example_ait")
+
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+local_repository(
+ name = "rules_utp",
+ path = "../../",
+)
+load("@rules_utp//:prereqs.bzl", "rules_utp_prereqs")
+rules_utp_prereqs()
+
+load("@rules_utp//:depprereqs.bzl", "rules_utp_dep_prereqs")
+rules_utp_dep_prereqs()
+
+load("@rules_utp//:defs.bzl", "rules_utp_workspace")
+rules_utp_workspace()
+
+load("defs.bzl", "example_workspace")
+example_workspace()
+
+maybe(
+ android_sdk_repository,
+ name = "androidsdk",
+)
+
+
+register_toolchains("@rules_android//toolchains/android:all")
+register_toolchains("@rules_android//toolchains/android_sdk:all")
+register_toolchains("@rules_android//toolchains/emulator:all")
diff --git a/example/ait/WORKSPACE.bzlmod b/example/ait/WORKSPACE.bzlmod
new file mode 100644
index 0000000..b02aad9
--- /dev/null
+++ b/example/ait/WORKSPACE.bzlmod
@@ -0,0 +1,9 @@
+workspace(name = "example_ait")
+
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("@rules_android//rules:rules.bzl", "android_sdk_repository")
+
+maybe(
+ android_sdk_repository,
+ name = "androidsdk",
+)
diff --git a/example/ait/defs.bzl b/example/ait/defs.bzl
new file mode 100644
index 0000000..0e3afad
--- /dev/null
+++ b/example/ait/defs.bzl
@@ -0,0 +1,55 @@
+# Copyright 2024 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.
+
+"""Workspace setup macro for rules_android."""
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+
+def example_workspace():
+ """ Sets up workspace dependencies for rules_android."""
+
+ CORE_VERSION = "1.6.0-alpha05"
+ EXT_JUNIT_VERSION = "1.2.0-alpha03"
+ ESPRESSO_VERSION = "3.6.0-alpha03"
+ RUNNER_VERSION = "1.6.0-alpha06"
+ RULES_VERSION = "1.6.0-alpha03"
+
+ maven_install(
+ name = "android_maven",
+ artifacts = [
+ "com.android.support:multidex:1.0.3",
+ "com.android.support.test:runner:1.0.2",
+ ],
+ fetch_sources = True,
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+ )
+
+ maven_install(
+ name = "androidx_maven",
+ artifacts = [
+ "androidx.test:core:" + CORE_VERSION,
+ "androidx.test.espresso:espresso-core:" + ESPRESSO_VERSION,
+ "androidx.test.ext:junit:" + EXT_JUNIT_VERSION,
+ "androidx.test:runner:" + RUNNER_VERSION,
+ "androidx.test:rules:" + RULES_VERSION,
+ ],
+ fetch_sources = True,
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+ )
diff --git a/example/ait/lib/AndroidManifest.xml b/example/ait/lib/AndroidManifest.xml
new file mode 100644
index 0000000..4be44b5
--- /dev/null
+++ b/example/ait/lib/AndroidManifest.xml
@@ -0,0 +1,18 @@
+
+
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.sample;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.util.Log;
+
+/** Skeleton activity used for testing Unified Test Platform e2e. */
+public class SampleActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.sample_activity);
+ }
+}
diff --git a/example/ait/lib/SampleAndroidManifest.xml b/example/ait/lib/SampleAndroidManifest.xml
new file mode 100644
index 0000000..1bffb8d
--- /dev/null
+++ b/example/ait/lib/SampleAndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
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.sample;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.core.app.ActivityScenario;
+import com.sample.SampleActivity;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Sanity Espresso tests for the Unified Test Platform to use. */
+@RunWith(AndroidJUnit4.class)
+public class SampleTest {
+
+ @Before
+ public void setUp() {}
+
+ @Test
+ public void helloWorldDoesNotBlowUp() {
+ ActivityScenario.launch(SampleActivity.class);
+ }
+
+ @Test
+ public void helloWorldIsDisplayed() {
+ ActivityScenario.launch(SampleActivity.class);
+ System.out.println(
+ "helloWorldIsDisplayed... "
+ + withId(R.string.hello_world)
+ + " / "
+ + withId(R.string.app_name));
+ onView(withId(R.id.text_hello)).check(matches(withText("Hello world!")));
+ }
+}
diff --git a/example/ait/lib/res/drawable-hdpi/ic_launcher.png b/example/ait/lib/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..72ebaad
Binary files /dev/null and b/example/ait/lib/res/drawable-hdpi/ic_launcher.png differ
diff --git a/example/ait/lib/res/drawable-mdpi/ic_launcher.png b/example/ait/lib/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..5ad8cb4
Binary files /dev/null and b/example/ait/lib/res/drawable-mdpi/ic_launcher.png differ
diff --git a/example/ait/lib/res/drawable-xhdpi/ic_launcher.png b/example/ait/lib/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a734148
Binary files /dev/null and b/example/ait/lib/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/example/ait/lib/res/drawable-xxhdpi/ic_launcher.png b/example/ait/lib/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..585e71d
Binary files /dev/null and b/example/ait/lib/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/example/ait/lib/res/layout/sample_activity.xml b/example/ait/lib/res/layout/sample_activity.xml
new file mode 100644
index 0000000..2451aef
--- /dev/null
+++ b/example/ait/lib/res/layout/sample_activity.xml
@@ -0,0 +1,17 @@
+