From 14632e2d621e142e768dd830cd44f1bbf57b4dae Mon Sep 17 00:00:00 2001 From: Kamil Wawrzyczek Date: Thu, 17 Mar 2022 10:48:36 +0100 Subject: [PATCH] Add TestStorageResultsWriter to write test results into sdcard when using androidx test orchestrator --- .../ExternalStoragePermissionsListener.kt | 24 +++++++++++++++ .../runners/AllureAndroidJUnitRunners.kt | 25 ++++++++++++++-- .../writer/TestStorageResultsWriter.kt | 30 +++++++++++++++++++ buildSrc/src/main/kotlin/Versions.kt | 3 +- samples/junit4-android/build.gradle.kts | 1 + .../androidTest/resources/allure.properties | 1 + 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/listeners/ExternalStoragePermissionsListener.kt create mode 100644 allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/writer/TestStorageResultsWriter.kt diff --git a/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/listeners/ExternalStoragePermissionsListener.kt b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/listeners/ExternalStoragePermissionsListener.kt new file mode 100644 index 0000000..3deedd9 --- /dev/null +++ b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/listeners/ExternalStoragePermissionsListener.kt @@ -0,0 +1,24 @@ +package io.qameta.allure.android.listeners + +import android.os.Build +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.runner.* +import org.junit.runner.notification.* + +class ExternalStoragePermissionsListener : RunListener() { + + override fun testRunStarted(description: Description?) { + InstrumentationRegistry.getInstrumentation().uiAutomation.apply { + val testServicesPackage = "androidx.test.services" + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { + executeShellCommand("appops set $testServicesPackage MANAGE_EXTERNAL_STORAGE allow") + } + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> { + executeShellCommand("pm grant $testServicesPackage android.permission.READ_EXTERNAL_STORAGE") + executeShellCommand("pm grant $testServicesPackage android.permission.WRITE_EXTERNAL_STORAGE") + } + } + } + } +} \ No newline at end of file diff --git a/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/runners/AllureAndroidJUnitRunners.kt b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/runners/AllureAndroidJUnitRunners.kt index 1da442b..894e6fa 100644 --- a/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/runners/AllureAndroidJUnitRunners.kt +++ b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/runners/AllureAndroidJUnitRunners.kt @@ -6,8 +6,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.runner.AndroidJUnitRunner import io.qameta.allure.android.AllureAndroidLifecycle import io.qameta.allure.android.internal.isDeviceTest +import io.qameta.allure.android.listeners.ExternalStoragePermissionsListener +import io.qameta.allure.android.writer.TestStorageResultsWriter import io.qameta.allure.kotlin.Allure import io.qameta.allure.kotlin.junit4.AllureJunit4 +import io.qameta.allure.kotlin.util.PropertiesUtils import org.junit.runner.* import org.junit.runner.manipulation.* import org.junit.runner.notification.* @@ -50,7 +53,8 @@ open class AllureAndroidJUnit4(clazz: Class<*>) : Runner(), Filterable, Sortable return AllureJunit4(androidLifecycle) } - protected open fun createAllureAndroidLifecycle() : AllureAndroidLifecycle = AllureAndroidLifecycle() + protected open fun createAllureAndroidLifecycle() : AllureAndroidLifecycle = + createDefaultAllureAndroidLifecycle() /** * Creates listener for tests running in an emulated Robolectric environment. @@ -76,13 +80,15 @@ open class AllureAndroidJUnitRunner : AndroidJUnitRunner() { Allure.lifecycle = createAllureAndroidLifecycle() val listenerArg = listOfNotNull( arguments.getCharSequence("listener"), - AllureJunit4::class.java.name + AllureJunit4::class.java.name, + ExternalStoragePermissionsListener::class.java.name.takeIf { useTestStorage } ).joinToString(separator = ",") arguments.putCharSequence("listener", listenerArg) super.onCreate(arguments) } - protected open fun createAllureAndroidLifecycle() : AllureAndroidLifecycle = AllureAndroidLifecycle() + protected open fun createAllureAndroidLifecycle() : AllureAndroidLifecycle = + createDefaultAllureAndroidLifecycle() } /** @@ -96,3 +102,16 @@ open class MultiDexAllureAndroidJUnitRunner : AllureAndroidJUnitRunner() { } } +private fun createDefaultAllureAndroidLifecycle() : AllureAndroidLifecycle { + if (useTestStorage) { + return AllureAndroidLifecycle(TestStorageResultsWriter()) + } + + return AllureAndroidLifecycle() +} + +private val useTestStorage: Boolean + get() = PropertiesUtils.loadAllureProperties() + .getProperty("allure.results.useTestStorage", "false") + .toBoolean() + diff --git a/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/writer/TestStorageResultsWriter.kt b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/writer/TestStorageResultsWriter.kt new file mode 100644 index 0000000..e7504ad --- /dev/null +++ b/allure-kotlin-android/src/main/kotlin/io/qameta/allure/android/writer/TestStorageResultsWriter.kt @@ -0,0 +1,30 @@ +package io.qameta.allure.android.writer + +import androidx.test.services.storage.TestStorage +import io.qameta.allure.kotlin.AllureResultsWriter +import io.qameta.allure.kotlin.OutputStreamResultsWriter +import io.qameta.allure.kotlin.model.TestResult +import io.qameta.allure.kotlin.model.TestResultContainer +import io.qameta.allure.kotlin.util.PropertiesUtils +import java.io.InputStream + +class TestStorageResultsWriter : AllureResultsWriter { + private val defaultAllurePath by lazy { PropertiesUtils.resultsDirectoryPath } + private val testStorage by lazy { TestStorage() } + + private val outputStreamResultsWriter = OutputStreamResultsWriter { name -> + testStorage.openOutputFile("$defaultAllurePath/$name") + } + + override fun write(testResult: TestResult) { + outputStreamResultsWriter.write(testResult) + } + + override fun write(testResultContainer: TestResultContainer) { + outputStreamResultsWriter.write(testResultContainer) + } + + override fun write(source: String, attachment: InputStream) { + outputStreamResultsWriter.write(source, attachment) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 323ad28..be97425 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -24,7 +24,8 @@ object Versions { const val multiDex = "2.0.1" object Test { - const val runner = "1.3.0" + const val orchestrator = "1.4.1" + const val runner = "1.4.0" const val junit = "1.1.2" const val espresso = "3.3.0" const val robolectric = "4.3.1" diff --git a/samples/junit4-android/build.gradle.kts b/samples/junit4-android/build.gradle.kts index 9d6d93e..0a5511c 100644 --- a/samples/junit4-android/build.gradle.kts +++ b/samples/junit4-android/build.gradle.kts @@ -52,4 +52,5 @@ dependencies { } testImplementation("org.robolectric:robolectric:${Versions.Android.Test.robolectric}") + androidTestUtil("androidx.test:orchestrator:${Versions.Android.Test.orchestrator}") } \ No newline at end of file diff --git a/samples/junit4-android/src/androidTest/resources/allure.properties b/samples/junit4-android/src/androidTest/resources/allure.properties index c858edb..02bcddd 100644 --- a/samples/junit4-android/src/androidTest/resources/allure.properties +++ b/samples/junit4-android/src/androidTest/resources/allure.properties @@ -1,2 +1,3 @@ +allure.results.useTestStorage=true allure.results.directory=allure-results allure.link.issue.pattern=https://jira.domain-name.com/browse/{}