aboutsummaryrefslogtreecommitdiffstats
path: root/utp
diff options
context:
space:
mode:
authorYuki Hamada <hummer@google.com>2021-03-12 12:07:19 -0800
committerYuki Hamada <hummer@google.com>2021-03-13 02:55:36 +0000
commit7ab0414f82a0968efe7edb40018f1f8b6c69ab62 (patch)
tree6ca5c4b4ecb965c26c68c2ab9b4d1577e618ecb4 /utp
parenta0cc6707ebddf239e76ad5c5b82c5520448c5ee2 (diff)
downloadplatform_tools_adt_idea-7ab0414f82a0968efe7edb40018f1f8b6c69ab62.tar.gz
platform_tools_adt_idea-7ab0414f82a0968efe7edb40018f1f8b6c69ab62.tar.bz2
platform_tools_adt_idea-7ab0414f82a0968efe7edb40018f1f8b6c69ab62.zip
Processes UTP test results XML tag in stdout text from AGP task
Bug: n/a Test: TaskOutputProcessorTest Change-Id: Icd5996ccb33ce8434383e8e75e46b88ec8b29247
Diffstat (limited to 'utp')
-rw-r--r--utp/BUILD11
-rw-r--r--utp/src/com/android/tools/utp/TaskOutputProcessor.kt91
-rw-r--r--utp/src/com/android/tools/utp/TaskOutputProcessorListener.kt52
-rw-r--r--utp/testSrc/com/android/tools/utp/TaskOutputProcessorTest.kt84
-rw-r--r--utp/utp.iml12
5 files changed, 250 insertions, 0 deletions
diff --git a/utp/BUILD b/utp/BUILD
index 72d27f1c517..65cdfdd1490 100644
--- a/utp/BUILD
+++ b/utp/BUILD
@@ -6,11 +6,15 @@ iml_module(
srcs = ["src"],
iml_files = ["utp.iml"],
resources = ["resources"],
+ test_srcs = ["testSrc"],
visibility = ["//visibility:public"],
# do not sort: must match IML order
deps = [
"//prebuilts/studio/intellij-sdk:studio-sdk-plugin-gradle",
"//prebuilts/studio/intellij-sdk:studio-sdk",
+ "//tools/adt/idea/utp:libstudio.android-test-plugin-result-listener-gradle-proto",
+ "//tools/adt/idea/.idea/libraries:mockito[test]",
+ "//tools/adt/idea/.idea/libraries:truth[test]",
],
)
@@ -23,3 +27,10 @@ genrule(
cmd = "cp $< $@",
visibility = ["//visibility:public"],
)
+
+# managed by go/iml_to_build
+java_import(
+ name = "libstudio.android-test-plugin-result-listener-gradle-proto",
+ jars = ["//tools/base/utp/android-test-plugin-result-listener-gradle-proto:libstudio.android-test-plugin-result-listener-gradle-proto.jar"],
+ visibility = ["//visibility:public"],
+)
diff --git a/utp/src/com/android/tools/utp/TaskOutputProcessor.kt b/utp/src/com/android/tools/utp/TaskOutputProcessor.kt
new file mode 100644
index 00000000000..500eb4052fc
--- /dev/null
+++ b/utp/src/com/android/tools/utp/TaskOutputProcessor.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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.tools.utp
+
+import com.android.tools.utp.plugins.result.listener.gradle.proto.GradleAndroidTestResultListenerProto.TestResultEvent
+import java.util.Base64
+
+/**
+ * Processes UTP test results XML tags in stdout text from Gradle task.
+ *
+ * @param listeners a list of listeners to be notified of test events
+ */
+class TaskOutputProcessor(val listeners: List<TaskOutputProcessorListener>) {
+
+ companion object {
+ const val ON_RESULT_OPENING_TAG = "<UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>"
+ const val ON_RESULT_CLOSING_TAG = "</UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>"
+ const val ON_COMPLETED_TAG = "<UTP_TEST_RESULT_ON_COMPLETED />"
+ const val ON_ERROR_TAG = "<UTP_TEST_RESULT_ON_ERROR />"
+ }
+
+ /**
+ * Processes stdout text from the Gradle task output. The input string can be
+ * multi-line string. The UTP test results XML tags are removed from the
+ * returning string.
+ *
+ * @param stdout a stdout text from the AGP task to be processed
+ */
+ fun process(stdout: String): String {
+ return stdout.lineSequence().filterNot(this::processLine).joinToString("\n")
+ }
+
+ private fun processLine(line: String): Boolean {
+ val trimmedLine = line.trim()
+ return when {
+ trimmedLine.startsWith(ON_RESULT_OPENING_TAG) && line.endsWith(ON_RESULT_CLOSING_TAG) -> {
+ val base64EncodedProto = trimmedLine.removeSurrounding(ON_RESULT_OPENING_TAG, ON_RESULT_CLOSING_TAG)
+ val eventProto = decodeBase64EncodedProto(base64EncodedProto)
+ processEvent(eventProto)
+ true
+ }
+ trimmedLine == ON_ERROR_TAG -> {
+ listeners.forEach(TaskOutputProcessorListener::onError)
+ true
+ }
+ trimmedLine == ON_COMPLETED_TAG -> {
+ listeners.forEach(TaskOutputProcessorListener::onComplete)
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun processEvent(event: TestResultEvent) {
+ when(event.stateCase) {
+ TestResultEvent.StateCase.TEST_SUITE_STARTED -> {
+ listeners.forEach(TaskOutputProcessorListener::onTestSuiteStarted)
+ }
+ TestResultEvent.StateCase.TEST_CASE_STARTED -> {
+ listeners.forEach(TaskOutputProcessorListener::onTestCaseStarted)
+ }
+ TestResultEvent.StateCase.TEST_CASE_FINISHED -> {
+ listeners.forEach(TaskOutputProcessorListener::onTestCaseFinished)
+ }
+ TestResultEvent.StateCase.TEST_SUITE_FINISHED -> {
+ listeners.forEach(TaskOutputProcessorListener::onTestSuiteFinished)
+ }
+ else -> {}
+ }
+ }
+}
+
+/**
+ * Decodes base64 encoded text proto message into [TestResultEvent].
+ */
+private fun decodeBase64EncodedProto(base64EncodedProto: String): TestResultEvent {
+ return TestResultEvent.parseFrom(Base64.getDecoder().decode(base64EncodedProto))
+} \ No newline at end of file
diff --git a/utp/src/com/android/tools/utp/TaskOutputProcessorListener.kt b/utp/src/com/android/tools/utp/TaskOutputProcessorListener.kt
new file mode 100644
index 00000000000..a65736196c8
--- /dev/null
+++ b/utp/src/com/android/tools/utp/TaskOutputProcessorListener.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.tools.utp
+
+/**
+ * An interface to receive test progress from [TaskOutputProcessor].
+ */
+interface TaskOutputProcessorListener {
+ /**
+ * Called when a test suite execution is started.
+ */
+ fun onTestSuiteStarted()
+
+ /**
+ * Called when a test case execution is started.
+ */
+ fun onTestCaseStarted()
+
+ /**
+ * Called when a test case execution is finished.
+ */
+ fun onTestCaseFinished()
+
+ /**
+ * Called when a test suite execution is finished.
+ */
+ fun onTestSuiteFinished()
+
+ /**
+ * Called when an error happens in AGP/UTP communication. If this method is invoked,
+ * it's the last method call and no more methods including [onComplete] are invoked.
+ */
+ fun onError()
+
+ /**
+ * Called when all test result events are processed successfully.
+ */
+ fun onComplete()
+} \ No newline at end of file
diff --git a/utp/testSrc/com/android/tools/utp/TaskOutputProcessorTest.kt b/utp/testSrc/com/android/tools/utp/TaskOutputProcessorTest.kt
new file mode 100644
index 00000000000..77042a3d87b
--- /dev/null
+++ b/utp/testSrc/com/android/tools/utp/TaskOutputProcessorTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.tools.utp
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verifyNoInteractions
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+/**
+ * Unit tests for [TaskOutputProcessor].
+ */
+@RunWith(JUnit4::class)
+class TaskOutputProcessorTest {
+
+ @get:Rule
+ val rule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var mockListener: TaskOutputProcessorListener
+
+ @Test
+ fun processNoUtpTag() {
+ val input = """
+ There are no UTP test result tag in this test input.
+ So the input text should be returned as-is.
+ """.trimIndent()
+ val processor = TaskOutputProcessor(listOf(mockListener))
+
+ val processed = processor.process(input)
+
+ assertThat(processed).isEqualTo(input)
+ verifyNoInteractions(mockListener)
+ }
+
+ @Test
+ fun processWithUtpTag() {
+ val input = """
+ Connected to process 6763 on device 'Pixel_3a_XL_API_28 [emulator-5554]'.
+ <UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>CgAqDWVtdWxhdG9yLTU1NTQ=</UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>
+ <UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>EgAqDWVtdWxhdG9yLTU1NTQ=</UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>
+ <UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>GgAqDWVtdWxhdG9yLTU1NTQ=</UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>
+ <UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>IgAqDWVtdWxhdG9yLTU1NTQ=</UTP_TEST_RESULT_ON_TEST_RESULT_EVENT>
+ <UTP_TEST_RESULT_ON_COMPLETED />
+ > Task :app:connectedDebugAndroidTest
+ """.trimIndent()
+ val processor = TaskOutputProcessor(listOf(mockListener))
+
+ val processed = processor.process(input)
+
+ assertThat(processed).isEqualTo("""
+ Connected to process 6763 on device 'Pixel_3a_XL_API_28 [emulator-5554]'.
+ > Task :app:connectedDebugAndroidTest
+ """.trimIndent())
+
+ inOrder(mockListener).apply {
+ verify(mockListener).onTestSuiteStarted()
+ verify(mockListener).onTestCaseStarted()
+ verify(mockListener).onTestCaseFinished()
+ verify(mockListener).onTestSuiteFinished()
+ verify(mockListener).onComplete()
+ verifyNoMoreInteractions()
+ }
+ }
+} \ No newline at end of file
diff --git a/utp/utp.iml b/utp/utp.iml
index 01a868d0582..52e2b6a56d4 100644
--- a/utp/utp.iml
+++ b/utp/utp.iml
@@ -5,10 +5,22 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+ <sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="studio-plugin-gradle" level="project" />
<orderEntry type="library" name="studio-sdk" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../bazel-bin/tools/base/utp/android-test-plugin-result-listener-gradle-proto/libstudio.android-test-plugin-result-listener-gradle-proto.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+ <orderEntry type="library" scope="TEST" name="truth" level="project" />
</component>
</module> \ No newline at end of file