diff options
| author | Yuki Hamada <hummer@google.com> | 2021-03-12 12:07:19 -0800 |
|---|---|---|
| committer | Yuki Hamada <hummer@google.com> | 2021-03-13 02:55:36 +0000 |
| commit | 7ab0414f82a0968efe7edb40018f1f8b6c69ab62 (patch) | |
| tree | 6ca5c4b4ecb965c26c68c2ab9b4d1577e618ecb4 /utp | |
| parent | a0cc6707ebddf239e76ad5c5b82c5520448c5ee2 (diff) | |
| download | platform_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/BUILD | 11 | ||||
| -rw-r--r-- | utp/src/com/android/tools/utp/TaskOutputProcessor.kt | 91 | ||||
| -rw-r--r-- | utp/src/com/android/tools/utp/TaskOutputProcessorListener.kt | 52 | ||||
| -rw-r--r-- | utp/testSrc/com/android/tools/utp/TaskOutputProcessorTest.kt | 84 | ||||
| -rw-r--r-- | utp/utp.iml | 12 |
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 |
