From b75b3c975a534d046d108c4c765a2a8d50b0c520 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 13 Jun 2024 15:48:39 +0200 Subject: [PATCH] Update AGP version to 7.4 (#353) * AGP Update * Update build files * Update more buildscripts * Fix formatting --- .editorconfig | 4 + .github/workflows/gradle-build.yml | 5 +- android-library-no-tests/build.gradle.kts | 16 +- .../src/main/AndroidManifest.xml | 2 +- .../flank/gradle/sample/MainActivity.kt | 1 - build.gradle | 12 +- fladle-plugin/build.gradle.kts | 14 +- .../com/osacky/flank/gradle/FladleConfig.kt | 19 +- .../osacky/flank/gradle/FladleConfigImpl.kt | 2 +- .../flank/gradle/FladlePluginDelegate.kt | 73 +- .../osacky/flank/gradle/FlankExecutionTask.kt | 30 +- .../flank/gradle/FlankGradleExtension.kt | 298 ++--- .../osacky/flank/gradle/FlankGradlePlugin.kt | 1 - .../com/osacky/flank/gradle/FlankJavaExec.kt | 22 +- .../flank/gradle/FulladleModuleExtension.kt | 79 +- .../com/osacky/flank/gradle/FulladlePlugin.kt | 94 +- .../flank/gradle/ImportReportDelegate.kt | 61 +- .../com/osacky/flank/gradle/RunFlankTask.kt | 17 +- .../flank/gradle/SanityConfigValidation.kt | 38 +- .../flank/gradle/YamlConfigWriterTask.kt | 80 +- .../com/osacky/flank/gradle/YamlExtensions.kt | 9 +- .../com/osacky/flank/gradle/YamlWriter.kt | 226 ++-- .../validation/ValidateExclusionUsage.kt | 18 +- .../gradle/validation/ValidateOptions.kt | 28 +- .../flank/gradle/MultipleConfigsTest.kt | 32 +- .../com/osacky/flank/gradle/YamlWriterTest.kt | 1100 +++++++++-------- .../gradle/integration/AndroidTestUtil.kt | 2 +- .../integration/AutoConfigureFladleTest.kt | 91 +- .../integration/ConfigurationCacheTest.kt | 11 +- .../gradle/integration/FlankAuthTestTask.kt | 2 +- .../FlankGradlePluginIntegrationTest.kt | 249 ++-- .../FulladlePluginIntegrationTest.kt | 954 +++++++------- .../gradle/integration/SanityRoboTest.kt | 33 +- .../SanityWithAutoConfigureTest.kt | 294 ++--- .../flank/gradle/integration/VariantTests.kt | 169 +-- .../validation/ValidateExclusionsTest.kt | 11 +- .../gradle/validation/ValidateOptionsTest.kt | 16 +- .../build.gradle | 15 +- .../src/main/AndroidManifest.xml | 5 +- .../android-library-project/build.gradle | 15 +- .../src/main/AndroidManifest.xml | 5 +- .../android-project-flavors/build.gradle | 19 +- .../src/main/AndroidManifest.xml | 5 +- .../resources/android-project/build.gradle | 17 +- .../src/main/AndroidManifest.xml | 5 +- .../resources/android-project2/build.gradle | 17 +- .../src/main/AndroidManifest.xml | 5 +- gradle/libs.versions.toml | 10 +- gradle/wrapper/gradle-wrapper.jar | Bin 61624 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 29 +- sample-android-library/build.gradle.kts | 16 +- .../gradle/sample/ExampleInstrumentedTest.kt | 1 - .../src/main/AndroidManifest.xml | 2 +- .../flank/gradle/sample/MainActivity.kt | 1 - sample-flavors-kotlin/build.gradle.kts | 56 +- .../gradle/sample/ExampleInstrumentedTest.kt | 1 - .../src/main/AndroidManifest.xml | 5 +- .../gradle/sample/kotlin/MainActivity.kt | 1 - sample-kotlin/build.gradle.kts | 30 +- .../gradle/sample/ExampleInstrumentedTest.kt | 1 - sample-kotlin/src/main/AndroidManifest.xml | 5 +- .../flank/gradle/sample/MainActivity.kt | 1 - sample/build.gradle | 17 +- .../gradle/sample/ExampleInstrumentedTest.kt | 1 - sample/src/main/AndroidManifest.xml | 5 +- .../flank/gradle/sample/MainActivity.kt | 1 - 67 files changed, 2357 insertions(+), 2050 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8e2a5955 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.{kt,kts}] +indent_size = 2 \ No newline at end of file diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml index 54bf30fc..4a81caf6 100644 --- a/.github/workflows/gradle-build.yml +++ b/.github/workflows/gradle-build.yml @@ -7,10 +7,11 @@ jobs: steps: - uses: actions/checkout@v4 - uses: gradle/actions/wrapper-validation@v3 - - uses: actions/setup-java@v4 + - name: "Set up JDK 11" + uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '8' + java-version: '11' - uses: gradle/actions/setup-gradle@v3 name: Setup Gradle - name: Check Fladle diff --git a/android-library-no-tests/build.gradle.kts b/android-library-no-tests/build.gradle.kts index 9ef01243..bed1b75d 100644 --- a/android-library-no-tests/build.gradle.kts +++ b/android-library-no-tests/build.gradle.kts @@ -4,12 +4,22 @@ plugins { } android { - compileSdkVersion(29) + compileSdk = 33 + namespace = "com.osacky.flank.gradle.sample.library" defaultConfig { - minSdkVersion(23) - targetSdkVersion(29) + minSdk = 23 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } fulladleModuleConfig { diff --git a/android-library-no-tests/src/main/AndroidManifest.xml b/android-library-no-tests/src/main/AndroidManifest.xml index 8487ce01..8072ee00 100644 --- a/android-library-no-tests/src/main/AndroidManifest.xml +++ b/android-library-no-tests/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt index c0132658..964eb395 100644 --- a/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ b/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } diff --git a/build.gradle b/build.gradle index 1296d1bc..20fdfeaf 100644 --- a/build.gradle +++ b/build.gradle @@ -13,24 +13,16 @@ plugins { alias(libs.plugins.kotlinter) } -kotlinter { - indentSize = 2 -} - allprojects { apply plugin: "org.jmailen.kotlinter" - - kotlinter { - indentSize = 2 - } } fladle { serviceAccountCredentials = project.layout.projectDirectory.file("sample/flank-gradle-5cf02dc90531.json") } -tasks.named('wrapper').configure { - gradleVersion = '7.6.3' +tasks.wrapper.configure { + gradleVersion = '8.4' } def isNonStable = { String version -> diff --git a/fladle-plugin/build.gradle.kts b/fladle-plugin/build.gradle.kts index 311ed2ca..14412bcb 100644 --- a/fladle-plugin/build.gradle.kts +++ b/fladle-plugin/build.gradle.kts @@ -36,10 +36,6 @@ dependencies { testImplementation(libs.truth) } -kotlinter { - indentSize = 2 -} - gradlePlugin { website.set("https://github.com/runningcode/fladle") vcsUrl.set("https://github.com/runningcode/fladle") @@ -136,17 +132,17 @@ tasks.withType(ValidatePlugins::class.java).configureEach { enableStricterValidation.set(true) } -// Ensure Java 8 Compatibility. See https://github.com/runningcode/fladle/issues/246 +// Ensure Java 11 Compatibility. See https://github.com/runningcode/fladle/issues/246 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).configureEach { kotlinOptions { - jvmTarget = "1.8" - languageVersion = "1.4" - apiVersion = "1.4" + jvmTarget = "11" + languageVersion = "1.7" + apiVersion = "1.7" } } java { toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(11)) } } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt index 30486811..869a9938 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt @@ -53,7 +53,7 @@ interface FladleConfig { @Deprecated( message = "testShards is deprecated. Use maxTestShards instead", - replaceWith = ReplaceWith("maxTestShards") + replaceWith = ReplaceWith("maxTestShards"), ) /** * The maximum number of shards. Value will be overwritten by [maxTestShards] if both used in configuration @@ -472,13 +472,14 @@ interface FladleConfig { val additionalGcloudOptions: Property @Internal - fun getPresentProperties() = this::class.memberProperties - .filter { - when (val prop = it.call(this)) { - is Property<*> -> prop.isPresent - is MapProperty<*, *> -> prop.isPresent && prop.get().isNotEmpty() - is ListProperty<*> -> prop.isPresent && prop.get().isNotEmpty() - else -> false + fun getPresentProperties() = + this::class.memberProperties + .filter { + when (val prop = it.call(this)) { + is Property<*> -> prop.isPresent + is MapProperty<*, *> -> prop.isPresent && prop.get().isNotEmpty() + is ListProperty<*> -> prop.isPresent && prop.get().isNotEmpty() + else -> false + } } - } } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt index 71437808..4105cad4 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt @@ -68,7 +68,7 @@ data class FladleConfigImpl( override val additionalFlankOptions: Property, override val additionalGcloudOptions: Property, override val dependOnAssemble: Property, - override val async: Property + override val async: Property, ) : FladleConfig { /** * Prepare config to run sanity robo. diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt index 02bf0527..ea878e0f 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt @@ -14,7 +14,6 @@ import org.gradle.kotlin.dsl.create import org.gradle.util.GradleVersion class FladlePluginDelegate { - fun apply(target: Project) { checkMinimumGradleVersion() @@ -41,7 +40,10 @@ class FladlePluginDelegate { } } - private fun configureTasks(project: Project, base: FlankGradleExtension) { + private fun configureTasks( + project: Project, + base: FlankGradleExtension, + ) { if (GradleVersion.current() > GradleVersion.version("6.1")) { base.flankVersion.finalizeValueOnRead() base.flankCoordinates.finalizeValueOnRead() @@ -70,21 +72,26 @@ class FladlePluginDelegate { } } - private fun TaskContainer.createTasksForConfig(base: FlankGradleExtension, config: FladleConfig, project: Project, name: String) { - + private fun TaskContainer.createTasksForConfig( + base: FlankGradleExtension, + config: FladleConfig, + project: Project, + name: String, + ) { val configName = name.toLowerCase() // we want to use default dir only if user did not set own `localResultsDir` val useDefaultDir = config.localResultsDir.isPresent.not() - val validateFladle = register("validateFladleConfig$name") { - description = "Perform validation actions" - group = TASK_GROUP - doLast { - checkIfSanityAndValidateConfigs(config) - validateOptionsUsed(config = config, flank = base.flankVersion.get()) - checkForExclusionUsage(config) + val validateFladle = + register("validateFladleConfig$name") { + description = "Perform validation actions" + group = TASK_GROUP + doLast { + checkIfSanityAndValidateConfigs(config) + validateOptionsUsed(config = config, flank = base.flankVersion.get()) + checkForExclusionUsage(config) + } } - } val writeConfigProps = register("writeConfigProps$name", YamlConfigWriterTask::class.java, base, config, name) @@ -112,17 +119,25 @@ class FladlePluginDelegate { if (useDefaultDir) setUpWorkingDir(configName) description = "Runs instrumentation tests using flank on firebase test lab." classpath = project.fladleConfig - args = if (project.hasProperty("dumpShards")) { - listOf("firebase", "test", "android", "run", "-c", writeConfigProps.get().fladleConfigFile.get().asFile.absolutePath, "--dump-shards") - } else { - listOf("firebase", "test", "android", "run", "-c", writeConfigProps.get().fladleConfigFile.get().asFile.absolutePath) - } + args = + if (project.hasProperty("dumpShards")) { + listOf( + "firebase", "test", "android", "run", "-c", + writeConfigProps.get().fladleConfigFile.get().asFile.absolutePath, "--dump-shards", + ) + } else { + listOf( + "firebase", "test", "android", "run", "-c", + writeConfigProps.get().fladleConfigFile.get().asFile.absolutePath, + ) + } if (config.serviceAccountCredentials.isPresent) { environment(mapOf("GOOGLE_APPLICATION_CREDENTIALS" to config.serviceAccountCredentials.get())) } dependsOn(writeConfigProps) if (config.dependOnAssemble.isPresent && config.dependOnAssemble.get()) { - val testedExtension = requireNotNull(project.extensions.findByType(TestedExtension::class.java)) { "Could not find TestedExtension in ${project.name}" } + val testedExtension = + requireNotNull(project.extensions.findByType(TestedExtension::class.java)) { "Could not find TestedExtension in ${project.name}" } testedExtension.testVariants.configureEach { if (testedVariant.isExpectedVariant(config)) { if (testedVariant.assembleProvider.isPresent) { @@ -154,10 +169,15 @@ class FladlePluginDelegate { } } - private fun automaticallyConfigureTestOrchestrator(project: Project, config: FladleConfig, androidExtension: AppExtension) { + private fun automaticallyConfigureTestOrchestrator( + project: Project, + config: FladleConfig, + androidExtension: AppExtension, + ) { project.afterEvaluate { - val useOrchestrator = androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROIDX_TEST_ORCHESTRATOR || - androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROID_TEST_ORCHESTRATOR + val useOrchestrator = + androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROIDX_TEST_ORCHESTRATOR || + androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROID_TEST_ORCHESTRATOR if (useOrchestrator) { log("Automatically detected the use of Android Test Orchestrator") } @@ -165,8 +185,12 @@ class FladlePluginDelegate { } } - private fun findDebugAndInstrumentationApk(project: Project, config: FladleConfig) { - val baseExtension = requireNotNull(project.extensions.findByType(AppExtension::class.java)) { "Could not find AppExtension in ${project.name}" } + private fun findDebugAndInstrumentationApk( + project: Project, + config: FladleConfig, + ) { + val baseExtension = + requireNotNull(project.extensions.findByType(AppExtension::class.java)) { "Could not find AppExtension in ${project.name}" } automaticallyConfigureTestOrchestrator(project, config, baseExtension) baseExtension.testVariants.configureEach { val appVariant = testedVariant @@ -195,9 +219,10 @@ class FladlePluginDelegate { get() = configurations.getByName(FLADLE_CONFIG) companion object { - val GRADLE_MIN_VERSION: GradleVersion = GradleVersion.version("5.5") + val GRADLE_MIN_VERSION: GradleVersion = GradleVersion.version("6.5") const val TASK_GROUP = "fladle" const val FLADLE_CONFIG = "fladle" + fun Project.log(message: String) { logger.info("Fladle: $message") } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt index 03fe141d..168c5712 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt @@ -5,18 +5,26 @@ import org.gradle.api.tasks.Nested import org.gradle.work.DisableCachingByDefault import javax.inject.Inject -@DisableCachingByDefault(because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.") -open class FlankExecutionTask @Inject constructor(projectLayout: ProjectLayout, @get:Nested val config: FladleConfig) : FlankJavaExec(projectLayout) { - - init { - doFirst { - checkFilesExist(config) +@DisableCachingByDefault( + because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", +) +open class FlankExecutionTask + @Inject + constructor( + projectLayout: ProjectLayout, + @get:Nested val config: FladleConfig, + ) : FlankJavaExec(projectLayout) { + init { + doFirst { + checkFilesExist(config) + } } - } - private fun checkFilesExist(base: FladleConfig) { - if (base.serviceAccountCredentials.isPresent) { - check(base.serviceAccountCredentials.get().asFile.exists()) { "serviceAccountCredential file doesn't exist ${base.serviceAccountCredentials.get()}" } + private fun checkFilesExist(base: FladleConfig) { + if (base.serviceAccountCredentials.isPresent) { + check(base.serviceAccountCredentials.get().asFile.exists()) { + "serviceAccountCredential file doesn't exist ${base.serviceAccountCredentials.get()}" + } + } } } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt index 540dce79..8c333688 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt @@ -14,208 +14,214 @@ import org.gradle.kotlin.dsl.mapProperty import org.gradle.kotlin.dsl.property import javax.inject.Inject -open class FlankGradleExtension @Inject constructor(objects: ObjectFactory) : FladleConfig { +open class FlankGradleExtension + @Inject + constructor(objects: ObjectFactory) : FladleConfig { + companion object { + const val FLANK_VERSION = "23.10.1" + } - companion object { - const val FLANK_VERSION = "23.10.1" - } - - @get:Input - val flankCoordinates: Property = objects.property(String::class.java).convention("com.github.flank:flank") + @get:Input + val flankCoordinates: Property = objects.property(String::class.java).convention("com.github.flank:flank") - override val sanityRobo: Property = objects.property().convention(false) + override val sanityRobo: Property = objects.property().convention(false) - @get:Input - val flankVersion: Property = objects.property(String::class.java).convention(FLANK_VERSION) - // Project id is automatically discovered by default. Use this to override the project id. - override val projectId: Property = objects.property() - override val serviceAccountCredentials: RegularFileProperty = objects.fileProperty() - override val useOrchestrator: Property = objects.property().convention(false) - override val autoGoogleLogin: Property = objects.property().convention(false) - override val devices: ListProperty> = objects.listProperty>().convention(listOf(mapOf("model" to "NexusLowRes", "version" to "28"))) + @get:Input + val flankVersion: Property = objects.property(String::class.java).convention(FLANK_VERSION) - // https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run - override val testTargets: ListProperty = objects.listProperty() + // Project id is automatically discovered by default. Use this to override the project id. + override val projectId: Property = objects.property() + override val serviceAccountCredentials: RegularFileProperty = objects.fileProperty() + override val useOrchestrator: Property = objects.property().convention(false) + override val autoGoogleLogin: Property = objects.property().convention(false) + override val devices: ListProperty> = + objects.listProperty>().convention( + listOf(mapOf("model" to "NexusLowRes", "version" to "28")), + ) - override val testShards: Property = objects.property() - override val shardTime: Property = objects.property() - override val repeatTests: Property = objects.property() + // https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run + override val testTargets: ListProperty = objects.listProperty() - // Shard Android tests by time using historical run data. The amount of shards used is set by `testShards`. - override val smartFlankGcsPath: Property = objects.property() + override val testShards: Property = objects.property() + override val shardTime: Property = objects.property() + override val repeatTests: Property = objects.property() - override val resultsHistoryName: Property = objects.property() + // Shard Android tests by time using historical run data. The amount of shards used is set by `testShards`. + override val smartFlankGcsPath: Property = objects.property() - override val flakyTestAttempts: Property = objects.property().convention(0) + override val resultsHistoryName: Property = objects.property() - override val variant: Property = objects.property() + override val flakyTestAttempts: Property = objects.property().convention(0) - override val abi: Property = objects.property() + override val variant: Property = objects.property() - /** - * debugApk and instrumentationApk are [Property] and not [RegularFileProperty] because we support wildcard characters. - */ - override val debugApk: Property = objects.property() + override val abi: Property = objects.property() - override val instrumentationApk: Property = objects.property() + /** + * debugApk and instrumentationApk are [Property] and not [RegularFileProperty] because we support wildcard characters. + */ + override val debugApk: Property = objects.property() - override val directoriesToPull: ListProperty = objects.listProperty() + override val instrumentationApk: Property = objects.property() - override val filesToDownload: ListProperty = objects.listProperty() + override val directoriesToPull: ListProperty = objects.listProperty() - override val environmentVariables: MapProperty = objects.mapProperty() + override val filesToDownload: ListProperty = objects.listProperty() - override val recordVideo: Property = objects.property().convention(true) + override val environmentVariables: MapProperty = objects.mapProperty() - override val performanceMetrics: Property = objects.property().convention(true) + override val recordVideo: Property = objects.property().convention(true) - override val resultsBucket: Property = objects.property() + override val performanceMetrics: Property = objects.property().convention(true) - override val keepFilePath: Property = objects.property().convention(false) + override val resultsBucket: Property = objects.property() - override val resultsDir: Property = objects.property() + override val keepFilePath: Property = objects.property().convention(false) - override val additionalTestApks: ListProperty = objects.listProperty() + override val resultsDir: Property = objects.property() - override val runTimeout: Property = objects.property() + override val additionalTestApks: ListProperty = objects.listProperty() - override val ignoreFailedTests: Property = objects.property().convention(false) + override val runTimeout: Property = objects.property() - override val disableSharding: Property = objects.property().convention(false) + override val ignoreFailedTests: Property = objects.property().convention(false) - override val smartFlankDisableUpload: Property = objects.property().convention(false) + override val disableSharding: Property = objects.property().convention(false) - override val testRunnerClass: Property = objects.property() + override val smartFlankDisableUpload: Property = objects.property().convention(false) - override val localResultsDir: Property = objects.property() + override val testRunnerClass: Property = objects.property() - override val numUniformShards: Property = objects.property() + override val localResultsDir: Property = objects.property() - override val clientDetails: MapProperty = objects.mapProperty() + override val numUniformShards: Property = objects.property() - override val testTargetsAlwaysRun: ListProperty = objects.listProperty() + override val clientDetails: MapProperty = objects.mapProperty() - override val otherFiles: MapProperty = objects.mapProperty() + override val testTargetsAlwaysRun: ListProperty = objects.listProperty() - override val networkProfile: Property = objects.property() + override val otherFiles: MapProperty = objects.mapProperty() - override val roboScript: Property = objects.property() + override val networkProfile: Property = objects.property() - override val roboDirectives: ListProperty> = objects.listProperty() + override val roboScript: Property = objects.property() - override val testTimeout: Property = objects.property().convention("15m") + override val roboDirectives: ListProperty> = objects.listProperty() - override val outputStyle: Property = objects.property().convention("single") + override val testTimeout: Property = objects.property().convention("15m") - override val legacyJunitResult: Property = objects.property().convention(false) + override val outputStyle: Property = objects.property().convention("single") - override val fullJunitResult: Property = objects.property().convention(false) + override val legacyJunitResult: Property = objects.property().convention(false) - override val additionalApks: ListProperty = objects.listProperty() + override val fullJunitResult: Property = objects.property().convention(false) - override val defaultTestTime: Property = objects.property() + override val additionalApks: ListProperty = objects.listProperty() - override val useAverageTestTimeForNewTests: Property = objects.property() + override val defaultTestTime: Property = objects.property() - override val defaultClassTestTime: Property = objects.property() + override val useAverageTestTimeForNewTests: Property = objects.property() - override val disableResultsUpload: Property = objects.property() + override val defaultClassTestTime: Property = objects.property() - override val testTargetsForShard: ListProperty = objects.listProperty() + override val disableResultsUpload: Property = objects.property() - override val grantPermissions: Property = objects.property() + override val testTargetsForShard: ListProperty = objects.listProperty() - override val type: Property = objects.property() + override val grantPermissions: Property = objects.property() - override val scenarioLabels: ListProperty = objects.listProperty() + override val type: Property = objects.property() - override val scenarioNumbers: ListProperty = objects.listProperty() + override val scenarioLabels: ListProperty = objects.listProperty() - override val obbFiles: ListProperty = objects.listProperty() + override val scenarioNumbers: ListProperty = objects.listProperty() - override val obbNames: ListProperty = objects.listProperty() + override val obbFiles: ListProperty = objects.listProperty() - override val failFast: Property = objects.property() + override val obbNames: ListProperty = objects.listProperty() - override val maxTestShards: Property = objects.property() + override val failFast: Property = objects.property() - override val additionalFlankOptions: Property = objects.property() + override val maxTestShards: Property = objects.property() - override val additionalGcloudOptions: Property = objects.property() + override val additionalFlankOptions: Property = objects.property() - override val dependOnAssemble: Property = objects.property().convention(false) + override val additionalGcloudOptions: Property = objects.property() - override val async: Property = objects.property().convention(false) + override val dependOnAssemble: Property = objects.property().convention(false) - @Internal - val configs: NamedDomainObjectContainer = objects.domainObjectContainer(FladleConfigImpl::class.java) { - FladleConfigImpl( - name = it, - projectId = objects.property().convention(projectId), - serviceAccountCredentials = objects.fileProperty().convention(serviceAccountCredentials), - debugApk = objects.property().convention(debugApk), - instrumentationApk = objects.property().convention(instrumentationApk), - sanityRobo = objects.property().convention(sanityRobo), - useOrchestrator = objects.property().convention(useOrchestrator), - autoGoogleLogin = objects.property().convention(autoGoogleLogin), - devices = objects.listProperty>().convention(devices), - testTargets = objects.listProperty().convention(testTargets), - testShards = objects.property().convention(testShards), - shardTime = objects.property().convention(shardTime), - repeatTests = objects.property().convention(repeatTests), - smartFlankGcsPath = objects.property().convention(smartFlankGcsPath), - resultsHistoryName = objects.property().convention(resultsHistoryName), - flakyTestAttempts = objects.property().convention(flakyTestAttempts), - variant = objects.property().convention(variant), - abi = objects.property().convention(abi), - directoriesToPull = objects.listProperty().convention(directoriesToPull), - filesToDownload = objects.listProperty().convention(filesToDownload), - environmentVariables = objects.mapProperty().convention(environmentVariables), - recordVideo = objects.property().convention(recordVideo), - performanceMetrics = objects.property().convention(performanceMetrics), - resultsBucket = objects.property().convention(resultsBucket), - keepFilePath = objects.property().convention(keepFilePath), - resultsDir = objects.property().convention(resultsDir), - additionalTestApks = objects.listProperty().convention(additionalTestApks), - runTimeout = objects.property().convention(runTimeout), - ignoreFailedTests = objects.property().convention(ignoreFailedTests), - disableSharding = objects.property().convention(disableSharding), - smartFlankDisableUpload = objects.property().convention(smartFlankDisableUpload), - testRunnerClass = objects.property().convention(testRunnerClass), - localResultsDir = objects.property().convention(localResultsDir), - numUniformShards = objects.property().convention(numUniformShards), - clientDetails = objects.mapProperty().convention(clientDetails), - testTargetsAlwaysRun = objects.listProperty().convention(testTargetsAlwaysRun), - otherFiles = objects.mapProperty().convention(otherFiles), - networkProfile = objects.property().convention(networkProfile), - roboScript = objects.property().convention(roboScript), - roboDirectives = objects.listProperty>().convention(roboDirectives), - testTimeout = objects.property().convention(testTimeout), - outputStyle = objects.property().convention(outputStyle), - legacyJunitResult = objects.property().convention(legacyJunitResult), - fullJunitResult = objects.property().convention(fullJunitResult), - additionalApks = objects.listProperty().convention(additionalApks), - useAverageTestTimeForNewTests = objects.property().convention(useAverageTestTimeForNewTests), - defaultTestTime = objects.property().convention(defaultTestTime), - defaultClassTestTime = objects.property().convention(defaultClassTestTime), - disableResultsUpload = objects.property().convention(disableResultsUpload), - testTargetsForShard = objects.listProperty().convention(testTargetsForShard), - grantPermissions = objects.property().convention(grantPermissions), - type = objects.property().convention(type), - scenarioLabels = objects.listProperty().convention(scenarioLabels), - scenarioNumbers = objects.listProperty().convention(scenarioNumbers), - obbFiles = objects.listProperty().convention(obbFiles), - obbNames = objects.listProperty().convention(obbNames), - failFast = objects.property().convention(failFast), - maxTestShards = objects.property().convention(maxTestShards), - additionalFlankOptions = objects.property().convention(additionalFlankOptions), - additionalGcloudOptions = objects.property().convention(additionalGcloudOptions), - dependOnAssemble = objects.property().convention(dependOnAssemble), - async = objects.property().convention(async) - ) - } + override val async: Property = objects.property().convention(false) - fun configs(closure: Closure<*>) { - configs.configure(closure) + @Internal + val configs: NamedDomainObjectContainer = + objects.domainObjectContainer(FladleConfigImpl::class.java) { + FladleConfigImpl( + name = it, + projectId = objects.property().convention(projectId), + serviceAccountCredentials = objects.fileProperty().convention(serviceAccountCredentials), + debugApk = objects.property().convention(debugApk), + instrumentationApk = objects.property().convention(instrumentationApk), + sanityRobo = objects.property().convention(sanityRobo), + useOrchestrator = objects.property().convention(useOrchestrator), + autoGoogleLogin = objects.property().convention(autoGoogleLogin), + devices = objects.listProperty>().convention(devices), + testTargets = objects.listProperty().convention(testTargets), + testShards = objects.property().convention(testShards), + shardTime = objects.property().convention(shardTime), + repeatTests = objects.property().convention(repeatTests), + smartFlankGcsPath = objects.property().convention(smartFlankGcsPath), + resultsHistoryName = objects.property().convention(resultsHistoryName), + flakyTestAttempts = objects.property().convention(flakyTestAttempts), + variant = objects.property().convention(variant), + abi = objects.property().convention(abi), + directoriesToPull = objects.listProperty().convention(directoriesToPull), + filesToDownload = objects.listProperty().convention(filesToDownload), + environmentVariables = objects.mapProperty().convention(environmentVariables), + recordVideo = objects.property().convention(recordVideo), + performanceMetrics = objects.property().convention(performanceMetrics), + resultsBucket = objects.property().convention(resultsBucket), + keepFilePath = objects.property().convention(keepFilePath), + resultsDir = objects.property().convention(resultsDir), + additionalTestApks = objects.listProperty().convention(additionalTestApks), + runTimeout = objects.property().convention(runTimeout), + ignoreFailedTests = objects.property().convention(ignoreFailedTests), + disableSharding = objects.property().convention(disableSharding), + smartFlankDisableUpload = objects.property().convention(smartFlankDisableUpload), + testRunnerClass = objects.property().convention(testRunnerClass), + localResultsDir = objects.property().convention(localResultsDir), + numUniformShards = objects.property().convention(numUniformShards), + clientDetails = objects.mapProperty().convention(clientDetails), + testTargetsAlwaysRun = objects.listProperty().convention(testTargetsAlwaysRun), + otherFiles = objects.mapProperty().convention(otherFiles), + networkProfile = objects.property().convention(networkProfile), + roboScript = objects.property().convention(roboScript), + roboDirectives = objects.listProperty>().convention(roboDirectives), + testTimeout = objects.property().convention(testTimeout), + outputStyle = objects.property().convention(outputStyle), + legacyJunitResult = objects.property().convention(legacyJunitResult), + fullJunitResult = objects.property().convention(fullJunitResult), + additionalApks = objects.listProperty().convention(additionalApks), + useAverageTestTimeForNewTests = objects.property().convention(useAverageTestTimeForNewTests), + defaultTestTime = objects.property().convention(defaultTestTime), + defaultClassTestTime = objects.property().convention(defaultClassTestTime), + disableResultsUpload = objects.property().convention(disableResultsUpload), + testTargetsForShard = objects.listProperty().convention(testTargetsForShard), + grantPermissions = objects.property().convention(grantPermissions), + type = objects.property().convention(type), + scenarioLabels = objects.listProperty().convention(scenarioLabels), + scenarioNumbers = objects.listProperty().convention(scenarioNumbers), + obbFiles = objects.listProperty().convention(obbFiles), + obbNames = objects.listProperty().convention(obbNames), + failFast = objects.property().convention(failFast), + maxTestShards = objects.property().convention(maxTestShards), + additionalFlankOptions = objects.property().convention(additionalFlankOptions), + additionalGcloudOptions = objects.property().convention(additionalGcloudOptions), + dependOnAssemble = objects.property().convention(dependOnAssemble), + async = objects.property().convention(async), + ) + } + + fun configs(closure: Closure<*>) { + configs.configure(closure) + } } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt index dfc8f437..3aa0b437 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt @@ -4,7 +4,6 @@ import org.gradle.api.Plugin import org.gradle.api.Project class FlankGradlePlugin : Plugin { - override fun apply(target: Project) { FladlePluginDelegate().apply(target) } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt index fc249c2a..f10ef850 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt @@ -5,13 +5,17 @@ import org.gradle.api.tasks.JavaExec import org.gradle.work.DisableCachingByDefault import javax.inject.Inject -@DisableCachingByDefault(because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.") -open class FlankJavaExec @Inject constructor(projectLayout: ProjectLayout) : JavaExec() { - init { - group = FladlePluginDelegate.TASK_GROUP - mainClass.set("ftl.Main") - workingDir(projectLayout.fladleDir) - } +@DisableCachingByDefault( + because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", +) +open class FlankJavaExec + @Inject + constructor(projectLayout: ProjectLayout) : JavaExec() { + init { + group = FladlePluginDelegate.TASK_GROUP + mainClass.set("ftl.Main") + workingDir(projectLayout.fladleDir) + } - fun setUpWorkingDir(configName: String) = workingDir(project.layout.buildDirectory.dir("fladle/$configName")) -} + fun setUpWorkingDir(configName: String) = workingDir(project.layout.buildDirectory.dir("fladle/$configName")) + } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt index 1ab414ee..510f5f54 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt @@ -7,42 +7,43 @@ import org.gradle.kotlin.dsl.mapProperty import org.gradle.kotlin.dsl.property import javax.inject.Inject -open class FulladleModuleExtension @Inject constructor(objects: ObjectFactory) { - - /** - * When set to false, Fulladle will not automatically add this module to additionalTestApks. - * - * Default: true - */ - val enabled: Property = objects.property().convention(true) - - /** - * The maximum number of shards to be used for this specific test apk. - */ - val maxTestShards: Property = objects.property().convention(null as Int?) - - /** - * A key-value map of additional details to attach to the test matrix results file. - * Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. - * When consuming the test results, such as in Cloud Functions or a CI system, - * these details can add additional context such as a link to the corresponding pull request. - */ - val clientDetails: MapProperty = objects.mapProperty() - - /** - * The environment variables are mirrored as extra options to the am instrument -e KEY1 VALUE1 … command and - * passed to your test runner (typically AndroidJUnitRunner) - */ - val environmentVariables: MapProperty = objects.mapProperty() - - /** - * the app under test - */ - val debugApk: Property = objects.property().convention(null as String?) - - /** - * The variant that should be used for the specific module. If nothing is specified any variant - * can be a match. - */ - val variant: Property = objects.property().convention(null as String?) -} +open class FulladleModuleExtension + @Inject + constructor(objects: ObjectFactory) { + /** + * When set to false, Fulladle will not automatically add this module to additionalTestApks. + * + * Default: true + */ + val enabled: Property = objects.property().convention(true) + + /** + * The maximum number of shards to be used for this specific test apk. + */ + val maxTestShards: Property = objects.property().convention(null as Int?) + + /** + * A key-value map of additional details to attach to the test matrix results file. + * Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. + * When consuming the test results, such as in Cloud Functions or a CI system, + * these details can add additional context such as a link to the corresponding pull request. + */ + val clientDetails: MapProperty = objects.mapProperty() + + /** + * The environment variables are mirrored as extra options to the am instrument -e KEY1 VALUE1 … command and + * passed to your test runner (typically AndroidJUnitRunner) + */ + val environmentVariables: MapProperty = objects.mapProperty() + + /** + * the app under test + */ + val debugApk: Property = objects.property().convention(null as String?) + + /** + * The variant that should be used for the specific module. If nothing is specified any variant + * can be a match. + */ + val variant: Property = objects.property().convention(null as String?) + } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt index ee7a1e7e..568feebb 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt @@ -20,44 +20,49 @@ class FulladlePlugin : Plugin { extensions.create("fulladleModuleConfig", FulladleModuleExtension::class.java) } - val fulladleConfigureTask = root.tasks.register("configureFulladle") { - var modulesEnabled = false - /** - * we will first configure all app modules - * then configure all library modules - * we force this order of configuration because - * app modules are better candidates to become - * root level test/app APKs, since they produce - * app APKs - * if no app module had tests or was enabled - * we will choose a library module to become - * a root level module, in which case we will - * have to check if it has its debugApk set - */ - doLast { - // first configure all app modules - root.subprojects { - if (!hasAndroidTest) - return@subprojects - modulesEnabled = true - if (isAndroidAppModule) - configureModule(this, flankGradleExtension) - } - // then configure all library modules - root.subprojects { - if (!hasAndroidTest) - return@subprojects - modulesEnabled = true - if (isAndroidLibraryModule) - configureModule(this, flankGradleExtension) - } + val fulladleConfigureTask = + root.tasks.register("configureFulladle") { + var modulesEnabled = false + /** + * we will first configure all app modules + * then configure all library modules + * we force this order of configuration because + * app modules are better candidates to become + * root level test/app APKs, since they produce + * app APKs + * if no app module had tests or was enabled + * we will choose a library module to become + * a root level module, in which case we will + * have to check if it has its debugApk set + */ + doLast { + // first configure all app modules + root.subprojects { + if (!hasAndroidTest) { + return@subprojects + } + modulesEnabled = true + if (isAndroidAppModule) { + configureModule(this, flankGradleExtension) + } + } + // then configure all library modules + root.subprojects { + if (!hasAndroidTest) { + return@subprojects + } + modulesEnabled = true + if (isAndroidLibraryModule) { + configureModule(this, flankGradleExtension) + } + } - check(modulesEnabled) { - "All modules were disabled for testing in fulladleModuleConfig or the enabled modules had no tests.\n" + - "Either re-enable modules for testing or add modules with tests." + check(modulesEnabled) { + "All modules were disabled for testing in fulladleModuleConfig or the enabled modules had no tests.\n" + + "Either re-enable modules for testing or add modules with tests." + } } } - } root.tasks.withType(YamlConfigWriterTask::class.java).configureEach { dependsOn(fulladleConfigureTask) @@ -72,7 +77,10 @@ class FulladlePlugin : Plugin { } } -fun configureModule(project: Project, flankGradleExtension: FlankGradleExtension) = project.run { +fun configureModule( + project: Project, + flankGradleExtension: FlankGradleExtension, +) = project.run { val fulladleModuleExtension = extensions.findByType(FulladleModuleExtension::class.java) ?: return if (!hasAndroidTest) { return @@ -102,7 +110,10 @@ fun configureModule(project: Project, flankGradleExtension: FlankGradleExtension // library modules do not produce an app apk and we'll use the one specified in fulladleModuleConfig block // we need library modules to specify the app apk to test against, even if it's a dummy one check(fulladleModuleExtension.debugApk.isPresent && fulladleModuleExtension.debugApk.orNull != null) { - "Library module ${project.path} did not specify a debug apk. Library modules do not generate a debug apk and one needs to be specified in the fulladleModuleConfig block\nThis is a required parameter in FTL which remains unused for library modules under test, and you can use a dummy apk here" + "Library module ${project.path} did not specify a debug apk. Library modules do not " + + "generate a debug apk and one needs to be specified in the fulladleModuleConfig block\n" + + "This is a required parameter in FTL which remains unused for library modules under test, " + + "and you can use a dummy apk here" } flankGradleExtension.debugApk.set(rootProject.provider { fulladleModuleExtension.debugApk.get() }) } @@ -139,11 +150,11 @@ fun configureModule(project: Project, flankGradleExtension: FlankGradleExtension yml.appendProperty(fulladleModuleExtension.maxTestShards, " max-test-shards") yml.appendMapProperty( fulladleModuleExtension.clientDetails, - " client-details" + " client-details", ) { appendLine(" ${it.key}: ${it.value}") } yml.appendMapProperty( fulladleModuleExtension.environmentVariables, - " environment-variables" + " environment-variables", ) { appendLine(" ${it.key}: ${it.value}") } flankGradleExtension.additionalTestApks.add(yml.toString()) } @@ -181,7 +192,10 @@ val Project.hasAndroidTest: Boolean return testsFound } -fun overrideRootLevelConfigs(flankGradleExtension: FlankGradleExtension, fulladleModuleExtension: FulladleModuleExtension) { +fun overrideRootLevelConfigs( + flankGradleExtension: FlankGradleExtension, + fulladleModuleExtension: FulladleModuleExtension, +) { // if the root module overrode any value in its fulladleModuleConfig block // then use those values instead val debugApk = fulladleModuleExtension.debugApk.orNull diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt index 9387eadd..f07314f2 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt @@ -15,31 +15,36 @@ import com.gradle.enterprise.gradleplugin.test.JUnitXmlDialect as GEJUnitXmlDial fun canImportReport(): Boolean = JUnitXmlHandler.canImport() -fun importReport(project: Project, flankTaskProvider: TaskProvider) { - val enableTestUploads = project.providers - .gradleProperty("com.osacky.fladle.enableTestUploads") - .getOrElse("true") - .toBoolean() +fun importReport( + project: Project, + flankTaskProvider: TaskProvider, +) { + val enableTestUploads = + project.providers + .gradleProperty("com.osacky.fladle.enableTestUploads") + .getOrElse("true") + .toBoolean() if (enableTestUploads) { - val resultsProvider: Provider = project.layout.buildDirectory - .dir("fladle") - .flatMap { fladleDir -> - val localResultsDirProvider: Provider = fladleDir - .dir(flankTaskProvider.flatMap { task -> task.config.localResultsDir }) + val resultsProvider: Provider = + project.layout.buildDirectory + .dir("fladle") + .flatMap { fladleDir -> + val localResultsDirProvider: Provider = + fladleDir + .dir(flankTaskProvider.flatMap { task -> task.config.localResultsDir }) - localResultsDirProvider.map { localResultsDir -> localResultsDir.file("JUnitReport.xml") } - } + localResultsDirProvider.map { localResultsDir -> localResultsDir.file("JUnitReport.xml") } + } JUnitXmlHandler.get()?.register( project.tasks, flankTaskProvider, - resultsProvider + resultsProvider, ) } } /** Abstraction over Develocity and GE impls of JUnitXml reporting. */ sealed class JUnitXmlHandler { - abstract fun register( tasks: TaskContainer, flankTask: TaskProvider, @@ -47,12 +52,13 @@ sealed class JUnitXmlHandler { ) companion object { - private fun canImport(name: String) = try { - Class.forName(name) - true - } catch (e: ClassNotFoundException) { - false - } + private fun canImport(name: String) = + try { + Class.forName(name) + true + } catch (e: ClassNotFoundException) { + false + } private val canImportDevelocity get() = canImport("com.gradle.develocity.agent.gradle.test.ImportJUnitXmlReports") @@ -60,13 +66,14 @@ sealed class JUnitXmlHandler { fun canImport() = canImportDevelocity || canImportGE - fun get() = if (canImportDevelocity) { - DevelocityJunitXmlHandler - } else if (canImportGE) { - GEJunitXmlHandler - } else { - null - } + fun get() = + if (canImportDevelocity) { + DevelocityJunitXmlHandler + } else if (canImportGE) { + GEJunitXmlHandler + } else { + null + } } object DevelocityJunitXmlHandler : JUnitXmlHandler() { diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt index 93d980c3..b47c0e74 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt @@ -4,11 +4,14 @@ import org.gradle.api.DefaultTask import org.gradle.work.DisableCachingByDefault import javax.inject.Inject -@DisableCachingByDefault(because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.") -open class RunFlankTask @Inject constructor() : DefaultTask() { - - init { - description = "Runs instrumentation tests using flank on firebase test lab." - group = FladlePluginDelegate.TASK_GROUP +@DisableCachingByDefault( + because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", +) +open class RunFlankTask + @Inject + constructor() : DefaultTask() { + init { + description = "Runs instrumentation tests using flank on firebase test lab." + group = FladlePluginDelegate.TASK_GROUP + } } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt index 48d8dfde..26d91f4a 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt @@ -4,23 +4,31 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import java.lang.IllegalArgumentException -fun checkIfSanityAndValidateConfigs(config: FladleConfig) = when (config) { - is FlankGradleExtension -> config.checkAndValidateConfig { option, name -> - "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo." +fun checkIfSanityAndValidateConfigs(config: FladleConfig) = + when (config) { + is FlankGradleExtension -> + config.checkAndValidateConfig { option, name -> + "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo." + } + is FladleConfigImpl -> + config.checkAndValidateConfig(config.name) { option, name -> + "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. " + + "To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" + } + else -> throw IllegalArgumentException("Unexpected configuration when validating parameters. Did not expect: $config.") } - is FladleConfigImpl -> config.checkAndValidateConfig(config.name) { option, name -> - "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. " + - "To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" - } - else -> throw IllegalArgumentException("Unexpected configuration when validating parameters. Did not expect: $config.") -} -private fun FladleConfig.checkAndValidateConfig(name: String = "base", message: (String, String) -> String) { - if (sanityRobo.get()) when { - roboDirectives.hasValue -> throw IllegalStateException(message("roboDirectives", name)) - roboScript.hasValue -> throw IllegalStateException(message("roboScript", name)) - instrumentationApk.hasValue -> throw IllegalStateException(message("instrumentationApk", name)) - additionalTestApks.hasValue -> throw IllegalStateException(message("additionalTestApks", name)) +private fun FladleConfig.checkAndValidateConfig( + name: String = "base", + message: (String, String) -> String, +) { + if (sanityRobo.get()) { + when { + roboDirectives.hasValue -> throw IllegalStateException(message("roboDirectives", name)) + roboScript.hasValue -> throw IllegalStateException(message("roboScript", name)) + instrumentationApk.hasValue -> throw IllegalStateException(message("instrumentationApk", name)) + additionalTestApks.hasValue -> throw IllegalStateException(message("additionalTestApks", name)) + } } } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt index 35473050..12373cbb 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt @@ -16,48 +16,52 @@ import java.util.Locale import javax.inject.Inject @DisableCachingByDefault(because = "The task writes a small file from in memory properties and does not benefit from caching.") -open class YamlConfigWriterTask @Inject constructor( - @get:Nested val base: FlankGradleExtension, - @get:Nested val config: FladleConfig, - @get:Input val configName: String, - projectLayout: ProjectLayout, - objects: ObjectFactory -) : DefaultTask() { - - private val yamlWriter = YamlWriter() - - private val fladleDir = projectLayout.fladleDir.map { - if (configName == "") { - it - } else { - it.dir(configName.toLowerCase(Locale.ROOT)) - } - } +open class YamlConfigWriterTask + @Inject + constructor( + @get:Nested val base: FlankGradleExtension, + @get:Nested val config: FladleConfig, + @get:Input val configName: String, + projectLayout: ProjectLayout, + objects: ObjectFactory, + ) : DefaultTask() { + private val yamlWriter = YamlWriter() - @get:Input - val additionalTestApks: ListProperty = objects.listProperty(String::class.java) - .convention(config.additionalTestApks) + private val fladleDir = + projectLayout.fladleDir.map { + if (configName == "") { + it + } else { + it.dir(configName.toLowerCase(Locale.ROOT)) + } + } - @OutputFile - val fladleConfigFile: Provider = fladleDir.map { it.file("flank.yml") } + @get:Input + val additionalTestApks: ListProperty = + objects.listProperty(String::class.java) + .convention(config.additionalTestApks) - @Internal - override fun getDescription(): String { - return "Writes a flank.yml file based on the current FlankGradleExtension configuration." - } + @OutputFile + val fladleConfigFile: Provider = fladleDir.map { it.file("flank.yml") } - @Internal - override fun getGroup(): String { - return FladlePluginDelegate.TASK_GROUP - } + @Internal + override fun getDescription(): String { + return "Writes a flank.yml file based on the current FlankGradleExtension configuration." + } + + @Internal + override fun getGroup(): String { + return FladlePluginDelegate.TASK_GROUP + } - @TaskAction - fun writeFile() { - fladleDir.get().asFile.mkdirs() - val mergedConfig = object : FladleConfig by config { - override val additionalTestApks: ListProperty - get() = this@YamlConfigWriterTask.additionalTestApks + @TaskAction + fun writeFile() { + fladleDir.get().asFile.mkdirs() + val mergedConfig = + object : FladleConfig by config { + override val additionalTestApks: ListProperty + get() = this@YamlConfigWriterTask.additionalTestApks + } + fladleConfigFile.get().asFile.writeText(yamlWriter.createConfigProps(mergedConfig, base)) } - fladleConfigFile.get().asFile.writeText(yamlWriter.createConfigProps(mergedConfig, base)) } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt index e02a6125..75d41a73 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt @@ -4,14 +4,17 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property -fun StringBuilder.appendProperty(prop: Property, name: String) { +fun StringBuilder.appendProperty( + prop: Property, + name: String, +) { if (prop.isPresent) appendLine(" $name: ${prop.get()}") } fun StringBuilder.appendMapProperty( prop: MapProperty, name: String, - custom: StringBuilder.(Map.Entry) -> Unit + custom: StringBuilder.(Map.Entry) -> Unit, ) { if (prop.isPresentAndNotEmpty) { appendLine(" $name:") @@ -22,7 +25,7 @@ fun StringBuilder.appendMapProperty( fun StringBuilder.appendListProperty( prop: ListProperty, name: String, - custom: StringBuilder.(T) -> Unit + custom: StringBuilder.(T) -> Unit, ) { if (prop.isPresentAndNotEmpty) { appendLine(" $name:") diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt index 962522c9..2262148f 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt @@ -3,23 +3,31 @@ package com.osacky.flank.gradle import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting internal class YamlWriter { - - internal fun createConfigProps(config: FladleConfig, base: FlankGradleExtension): String { + internal fun createConfigProps( + config: FladleConfig, + base: FlankGradleExtension, + ): String { fun Boolean.toInt() = if (this) 1 else 0 if (base.projectId.orNull == null) { - check(base.serviceAccountCredentials.isPresent) { "ServiceAccountCredentials in fladle extension not set. https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials" } + check(base.serviceAccountCredentials.isPresent) { + "ServiceAccountCredentials in fladle extension not set." + + "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials" + } } check(base.debugApk.isPresent) { "debugApk must be specified" } if (!config.sanityRobo.get()) { - val result = config.instrumentationApk.isPresent.toInt() + config.roboScript.hasValue.toInt() + config.roboDirectives.isPresentAndNotEmpty.toInt() + val result = + config.instrumentationApk.isPresent.toInt() + config.roboScript.hasValue.toInt() + + config.roboDirectives.isPresentAndNotEmpty.toInt() check(result == 1) { - val prefix = if (result > 1) { - "Only one of instrumentationApk file, roboScript file, and robo directives must be specified." - } else { - "Must specify either a instrumentationApk file or a roboScript file or a robo directive." - } + val prefix = + if (result > 1) { + "Only one of instrumentationApk file, roboScript file, and robo directives must be specified." + } else { + "Must specify either a instrumentationApk file or a roboScript file or a robo directive." + } """ $prefix instrumentationApk=${config.instrumentationApk.orNull} @@ -43,114 +51,118 @@ internal class YamlWriter { } } - internal fun writeFlankProperties(config: FladleConfig): String = buildString { - appendLine("flank:") + internal fun writeFlankProperties(config: FladleConfig): String = + buildString { + appendLine("flank:") - // Fladle will fail in configuration phase if both maxTestShards and testShards are present - appendProperty(config.maxTestShards, name = "max-test-shards") - appendProperty(config.testShards, name = "max-test-shards") + // Fladle will fail in configuration phase if both maxTestShards and testShards are present + appendProperty(config.maxTestShards, name = "max-test-shards") + appendProperty(config.testShards, name = "max-test-shards") - appendProperty(config.shardTime, name = "shard-time") - appendProperty(config.repeatTests, name = "num-test-runs") - appendProperty(config.smartFlankGcsPath, name = "smart-flank-gcs-path") - appendProperty(config.projectId, name = "project") - appendProperty(config.keepFilePath, name = "keep-file-path") - appendListProperty(config.filesToDownload, name = "files-to-download") { appendLine(" - $it") } - if (!config.sanityRobo.get()) { + appendProperty(config.shardTime, name = "shard-time") + appendProperty(config.repeatTests, name = "num-test-runs") + appendProperty(config.smartFlankGcsPath, name = "smart-flank-gcs-path") + appendProperty(config.projectId, name = "project") + appendProperty(config.keepFilePath, name = "keep-file-path") + appendListProperty(config.filesToDownload, name = "files-to-download") { appendLine(" - $it") } + if (!config.sanityRobo.get()) { + appendListProperty( + config.additionalTestApks, + name = "additional-app-test-apks", + ) { appendLine(" $it") } + } + appendProperty(config.runTimeout, name = "run-timeout") + appendProperty(config.ignoreFailedTests, name = "ignore-failed-tests") + appendProperty(config.disableSharding, name = "disable-sharding") + appendProperty(config.smartFlankDisableUpload, name = "smart-flank-disable-upload") + appendProperty(config.localResultsDir, name = "local-result-dir") appendListProperty( - config.additionalTestApks, - name = "additional-app-test-apks" - ) { appendLine(" $it") } - } - appendProperty(config.runTimeout, name = "run-timeout") - appendProperty(config.ignoreFailedTests, name = "ignore-failed-tests") - appendProperty(config.disableSharding, name = "disable-sharding") - appendProperty(config.smartFlankDisableUpload, name = "smart-flank-disable-upload") - appendProperty(config.localResultsDir, name = "local-result-dir") - appendListProperty( - config.testTargetsAlwaysRun, - name = "test-targets-always-run" - ) { appendLine(" - class $it") } - appendProperty(config.legacyJunitResult, name = "legacy-junit-result") - appendProperty(config.fullJunitResult, name = "full-junit-result") - appendProperty(config.outputStyle, name = "output-style") - appendProperty(config.defaultTestTime, name = "default-test-time") - appendProperty(config.defaultClassTestTime, name = "default-class-test-time") - appendProperty( - config.useAverageTestTimeForNewTests, - name = "use-average-test-time-for-new-tests" - ) - appendProperty(config.disableResultsUpload, name = "disable-results-upload") - appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { - appendLine(" - $it") + config.testTargetsAlwaysRun, + name = "test-targets-always-run", + ) { appendLine(" - class $it") } + appendProperty(config.legacyJunitResult, name = "legacy-junit-result") + appendProperty(config.fullJunitResult, name = "full-junit-result") + appendProperty(config.outputStyle, name = "output-style") + appendProperty(config.defaultTestTime, name = "default-test-time") + appendProperty(config.defaultClassTestTime, name = "default-class-test-time") + appendProperty( + config.useAverageTestTimeForNewTests, + name = "use-average-test-time-for-new-tests", + ) + appendProperty(config.disableResultsUpload, name = "disable-results-upload") + appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { + appendLine(" - $it") + } + appendAdditionalProperty(config.additionalFlankOptions) } - appendAdditionalProperty(config.additionalFlankOptions) - } - internal fun writeAdditionalProperties(config: FladleConfig): String = buildString { - appendProperty(config.useOrchestrator, name = "use-orchestrator") - appendProperty(config.autoGoogleLogin, name = "auto-google-login") - appendProperty(config.recordVideo, name = "record-video") - appendProperty(config.performanceMetrics, name = "performance-metrics") - appendProperty(config.testTimeout, name = "timeout") - if (config.async.isPresent && config.async.get()) { - appendProperty(config.async, name = "async") - } - appendProperty(config.resultsHistoryName, name = "results-history-name") - appendProperty(config.resultsBucket, name = "results-bucket") - appendMapProperty(config.environmentVariables, name = "environment-variables") { - appendLine(" ${it.key}: ${it.value}") - } - appendListProperty(config.testTargets, name = "test-targets") { appendLine(" - $it") } - appendListProperty(config.directoriesToPull, name = "directories-to-pull") { appendLine(" - $it") } - appendProperty(config.flakyTestAttempts, name = "num-flaky-test-attempts") - appendProperty(config.resultsDir, name = "results-dir") - appendProperty(config.testRunnerClass, name = "test-runner-class") - appendProperty(config.numUniformShards, name = "num-uniform-shards") - appendMapProperty(config.clientDetails, name = "client-details") { appendLine(" ${it.key}: ${it.value}") } - appendMapProperty(config.otherFiles, name = "other-files") { appendLine(" ${it.key}: ${it.value}") } - appendProperty(config.networkProfile, name = "network-profile") - if (!config.sanityRobo.get()) { - if (config.roboScript.hasValue) { - appendProperty(config.roboScript, name = "robo-script") - } else { - appendListProperty(config.roboDirectives, name = "robo-directives") { - val value = it.getOrElse(2) { "" } - .let { stringValue -> if (stringValue.isBlank()) "\"\"" else stringValue } - appendLine(" ${it[0]}:${it[1]}: $value") + internal fun writeAdditionalProperties(config: FladleConfig): String = + buildString { + appendProperty(config.useOrchestrator, name = "use-orchestrator") + appendProperty(config.autoGoogleLogin, name = "auto-google-login") + appendProperty(config.recordVideo, name = "record-video") + appendProperty(config.performanceMetrics, name = "performance-metrics") + appendProperty(config.testTimeout, name = "timeout") + if (config.async.isPresent && config.async.get()) { + appendProperty(config.async, name = "async") + } + appendProperty(config.resultsHistoryName, name = "results-history-name") + appendProperty(config.resultsBucket, name = "results-bucket") + appendMapProperty(config.environmentVariables, name = "environment-variables") { + appendLine(" ${it.key}: ${it.value}") + } + appendListProperty(config.testTargets, name = "test-targets") { appendLine(" - $it") } + appendListProperty(config.directoriesToPull, name = "directories-to-pull") { appendLine(" - $it") } + appendProperty(config.flakyTestAttempts, name = "num-flaky-test-attempts") + appendProperty(config.resultsDir, name = "results-dir") + appendProperty(config.testRunnerClass, name = "test-runner-class") + appendProperty(config.numUniformShards, name = "num-uniform-shards") + appendMapProperty(config.clientDetails, name = "client-details") { appendLine(" ${it.key}: ${it.value}") } + appendMapProperty(config.otherFiles, name = "other-files") { appendLine(" ${it.key}: ${it.value}") } + appendProperty(config.networkProfile, name = "network-profile") + if (!config.sanityRobo.get()) { + if (config.roboScript.hasValue) { + appendProperty(config.roboScript, name = "robo-script") + } else { + appendListProperty(config.roboDirectives, name = "robo-directives") { + val value = + it.getOrElse(2) { "" } + .let { stringValue -> if (stringValue.isBlank()) "\"\"" else stringValue } + appendLine(" ${it[0]}:${it[1]}: $value") + } } } + appendListProperty(config.additionalApks, name = "additional-apks") { appendLine(" - $it") } + appendProperty(config.grantPermissions, name = "grant-permissions") + appendProperty(config.type, name = "type") + appendListProperty(config.scenarioLabels, name = "scenario-labels") { appendLine(" - $it") } + appendListProperty(config.scenarioNumbers, name = "scenario-numbers") { appendLine(" - $it") } + appendListProperty(config.obbFiles, name = "obb-files") { appendLine(" - $it") } + appendListProperty(config.obbNames, name = "obb-names") { appendLine(" - $it") } + appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { appendLine(" - $it") } + appendProperty(config.failFast, name = "fail-fast") + appendAdditionalProperty(config.additionalGcloudOptions) } - appendListProperty(config.additionalApks, name = "additional-apks") { appendLine(" - $it") } - appendProperty(config.grantPermissions, name = "grant-permissions") - appendProperty(config.type, name = "type") - appendListProperty(config.scenarioLabels, name = "scenario-labels") { appendLine(" - $it") } - appendListProperty(config.scenarioNumbers, name = "scenario-numbers") { appendLine(" - $it") } - appendListProperty(config.obbFiles, name = "obb-files") { appendLine(" - $it") } - appendListProperty(config.obbNames, name = "obb-names") { appendLine(" - $it") } - appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { appendLine(" - $it") } - appendProperty(config.failFast, name = "fail-fast") - appendAdditionalProperty(config.additionalGcloudOptions) - } @VisibleForTesting - internal fun createDeviceString(devices: List>): String = buildString { - appendLine(" device:") - for (device in devices) { - if (device["model"] == null) throw RequiredDeviceKeyMissingException("model") - val model = device["model"] - if (device["version"] == null) throw RequiredDeviceKeyMissingException("version") - val version = device["version"] - val orientation = device["orientation"] - val locale = device["locale"] - appendLine(" - model: $model") - appendLine(" version: $version") - orientation?.let { - appendLine(" orientation: $it") - } - locale?.let { - appendLine(" locale: $it") + internal fun createDeviceString(devices: List>): String = + buildString { + appendLine(" device:") + for (device in devices) { + if (device["model"] == null) throw RequiredDeviceKeyMissingException("model") + val model = device["model"] + if (device["version"] == null) throw RequiredDeviceKeyMissingException("version") + val version = device["version"] + val orientation = device["orientation"] + val locale = device["locale"] + appendLine(" - model: $model") + appendLine(" version: $version") + orientation?.let { + appendLine(" orientation: $it") + } + locale?.let { + appendLine(" locale: $it") + } } } - } } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt index 1db0b061..c60daf31 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt @@ -3,9 +3,10 @@ package com.osacky.flank.gradle.validation import com.osacky.flank.gradle.FladleConfig fun checkForExclusionUsage(config: FladleConfig) { - val usersProperties = config - .getPresentProperties() - .map { it.name } + val usersProperties = + config + .getPresentProperties() + .map { it.name } exclusions.forEach { if (usersProperties.contains(it.first) && usersProperties.contains(it.second)) { @@ -14,8 +15,9 @@ fun checkForExclusionUsage(config: FladleConfig) { } } -private val exclusions = listOf( - "testShards" to "maxTestShards", - "testShards" to "numUniformShards", - "maxTestShards" to "numUniformShards" -) +private val exclusions = + listOf( + "testShards" to "maxTestShards", + "testShards" to "numUniformShards", + "maxTestShards" to "numUniformShards", + ) diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt index 5ba750d3..4de6a103 100644 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt +++ b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt @@ -5,24 +5,32 @@ import com.osacky.flank.gradle.FlankGradleExtension.Companion.FLANK_VERSION import org.gradle.util.VersionNumber import kotlin.reflect.full.memberProperties -fun validateOptionsUsed(config: FladleConfig, flank: String) { +fun validateOptionsUsed( + config: FladleConfig, + flank: String, +) { // if using snapshot version default to the latest known version of flank for validation checks val configFlankVersion = if (flank.toLowerCase().endsWith("snapshot")) FLANK_VERSION.toVersion() else flank.toVersion() config.getPresentProperties() .mapNotNull { property -> properties[property.name]?.let { property to it } } .forEach { (property, version) -> - if (version > configFlankVersion) throw IllegalStateException("Option ${property.name} is available since flank $version, which is higher than used $configFlankVersion") + if (version > configFlankVersion) { + throw IllegalStateException( + "Option ${property.name} is available since flank $version, which is higher than used $configFlankVersion", + ) + } } } private fun String.toVersion() = VersionNumber.parse(this) -private val properties = FladleConfig::class.memberProperties - .asSequence() - .map { it to it.getter.annotations } - // we also need to exclude properties with default values to preserve backward compatibility - // to be fixed - .filter { it.second.any { annotation -> annotation is SinceFlank && !annotation.hasDefaultValue } } - .map { it.first.name to it.second.filterIsInstance().first().version.toVersion() } - .toMap() +private val properties = + FladleConfig::class.memberProperties + .asSequence() + .map { it to it.getter.annotations } + // we also need to exclude properties with default values to preserve backward compatibility + // to be fixed + .filter { it.second.any { annotation -> annotation is SinceFlank && !annotation.hasDefaultValue } } + .map { it.first.name to it.second.filterIsInstance().first().version.toVersion() } + .toMap() diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt index 2c2884cc..03eb0ee3 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt @@ -33,16 +33,17 @@ class MultipleConfigsTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) testProjectRoot.newFile("flank-gradle-service.json").writeText("{}") - val result = GradleRunner.create() - .withPluginClasspath() - .withArguments("writeConfigPropsOrange", "--stacktrace") - .forwardOutput() - .withProjectDir(testProjectRoot.root) - .build() + val result = + GradleRunner.create() + .withPluginClasspath() + .withArguments("writeConfigPropsOrange", "--stacktrace") + .forwardOutput() + .withProjectDir(testProjectRoot.root) + .build() assertThat(result.output).contains("SUCCESS") @@ -71,15 +72,16 @@ class MultipleConfigsTest { | disable-sharding: false | smart-flank-disable-upload: false | local-result-dir: overrideDir - """.trimMargin() + """.trimMargin(), ) - val regularConfig = GradleRunner.create() - .withPluginClasspath() - .withArguments("writeConfigProps") - .forwardOutput() - .withProjectDir(testProjectRoot.root) - .build() + val regularConfig = + GradleRunner.create() + .withPluginClasspath() + .withArguments("writeConfigProps") + .forwardOutput() + .withProjectDir(testProjectRoot.root) + .build() assertThat(regularConfig.output).contains("SUCCESS") @@ -108,7 +110,7 @@ class MultipleConfigsTest { | disable-sharding: false | smart-flank-disable-upload: false | local-result-dir: defaultDir - """.trimMargin() + """.trimMargin(), ) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt index 11780e4b..044b5d33 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt @@ -10,7 +10,6 @@ import org.junit.Before import org.junit.Test class YamlWriterTest { - private val yamlWriter = YamlWriter() private lateinit var project: Project @@ -22,9 +21,10 @@ class YamlWriterTest { @Test fun testWriteSingleDevice() { - val devices = listOf( - mapOf("model" to "NexusLowRes", "version" to "28") - ) + val devices = + listOf( + mapOf("model" to "NexusLowRes", "version" to "28"), + ) val deviceString = yamlWriter.createDeviceString(devices) val expected = """ @@ -38,10 +38,11 @@ class YamlWriterTest { @Test fun testWriteTwoDevices() { - val devices = listOf( - mapOf("model" to "NexusLowRes", "version" to "28"), - mapOf("model" to "Nexus5", "version" to "23") - ) + val devices = + listOf( + mapOf("model" to "NexusLowRes", "version" to "28"), + mapOf("model" to "Nexus5", "version" to "23"), + ) val deviceString = yamlWriter.createDeviceString(devices) val expected = """ @@ -57,10 +58,11 @@ class YamlWriterTest { @Test fun testWriteTwoCustomDevices() { - val devices = listOf( - mapOf("model" to "NexusLowRes", "version" to "23", "orientation" to "portrait"), - mapOf("model" to "Nexus5", "orientation" to "landscape", "version" to "28") - ) + val devices = + listOf( + mapOf("model" to "NexusLowRes", "version" to "23", "orientation" to "portrait"), + mapOf("model" to "Nexus5", "orientation" to "landscape", "version" to "28"), + ) val deviceString = yamlWriter.createDeviceString(devices) val expected = """ @@ -78,10 +80,11 @@ class YamlWriterTest { @Test fun testWriteTwoCustomDevicesWithLocale() { - val devices = listOf( - mapOf("model" to "NexusLowRes", "version" to "23", "orientation" to "portrait", "locale" to "en"), - mapOf("model" to "Nexus5", "orientation" to "landscape", "locale" to "es_ES", "version" to "28") - ) + val devices = + listOf( + mapOf("model" to "NexusLowRes", "version" to "23", "orientation" to "portrait", "locale" to "en"), + mapOf("model" to "Nexus5", "orientation" to "landscape", "locale" to "es_ES", "version" to "28"), + ) val deviceString = yamlWriter.createDeviceString(devices) val expected = """ @@ -101,9 +104,10 @@ class YamlWriterTest { @Test fun testThrowsExceptionWhenMissingModelKeyInDevice() { - val devices = listOf( - mapOf("version" to "23", "orientation" to "portrait", "locale" to "en") - ) + val devices = + listOf( + mapOf("version" to "23", "orientation" to "portrait", "locale" to "en"), + ) try { yamlWriter.createDeviceString(devices) fail() @@ -114,9 +118,10 @@ class YamlWriterTest { @Test fun testThrowsExceptionWhenMissingVersionKeyInDevice() { - val devices = listOf( - mapOf("model" to "NexusLowRes", "orientation" to "portrait", "locale" to "en") - ) + val devices = + listOf( + mapOf("model" to "NexusLowRes", "orientation" to "portrait", "locale" to "en"), + ) try { yamlWriter.createDeviceString(devices) fail() @@ -133,54 +138,57 @@ class YamlWriterTest { fail() } catch (expected: IllegalStateException) { assertEquals( - "ServiceAccountCredentials in fladle extension not set. https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials", - expected.message + "ServiceAccountCredentials in fladle extension not set." + + "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials", + expected.message, ) } } @Test fun verifyMissingServiceDoesntThrowErrorIfProjectIdSet() { - val extension = emptyExtension { - projectId.set("set") - debugApk.set("path") - instrumentationApk.set("instrument") - } + val extension = + emptyExtension { + projectId.set("set") + debugApk.set("path") + instrumentationApk.set("instrument") + } val yaml = yamlWriter.createConfigProps(extension, extension) assertThat(yaml).isEqualTo( """ - gcloud: - app: path - test: instrument - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - project: set - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n' // Dunno why this needs to be here to make the tests pass. + gcloud: + app: path + test: instrument + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + + flank: + project: set + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent() + '\n', ) } @Test fun verifyDebugApkThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -191,10 +199,11 @@ class YamlWriterTest { @Test fun verifyNoInstrumentationApkThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -205,19 +214,20 @@ class YamlWriterTest { instrumentationApk=null roboScript=null roboDirective=[] - """.trimIndent() + """.trimIndent(), ) } } @Test fun verifyInstrumentationApkAndRoboscriptThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboScript.set("foo") - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + instrumentationApk.set("build/test/*.apk") + roboScript.set("foo") + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -228,19 +238,20 @@ class YamlWriterTest { instrumentationApk=build/test/*.apk roboScript=foo roboDirective=[] - """.trimIndent() + """.trimIndent(), ) } } @Test fun verifyInstrumentationApkAndRobodirectivesThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboDirectives.add(listOf("click", "resource_id")) - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + instrumentationApk.set("build/test/*.apk") + roboDirectives.add(listOf("click", "resource_id")) + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -251,19 +262,20 @@ class YamlWriterTest { instrumentationApk=build/test/*.apk roboScript=null roboDirective=[[click, resource_id]] - """.trimIndent() + """.trimIndent(), ) } } @Test fun verifyRoboscriptAndRobodirectivesThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboScript.set("foo") - roboDirectives.add(listOf("click", "resource_id")) - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + roboScript.set("foo") + roboDirectives.add(listOf("click", "resource_id")) + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -274,20 +286,21 @@ class YamlWriterTest { instrumentationApk=null roboScript=foo roboDirective=[[click, resource_id]] - """.trimIndent() + """.trimIndent(), ) } } @Test fun verifyInstrumentationApkAndRoboscriptAndRobodirectivesThrowsError() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboScript.set("foo") - roboDirectives.add(listOf("click", "resource_id")) - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + instrumentationApk.set("build/test/*.apk") + roboScript.set("foo") + roboDirectives.add(listOf("click", "resource_id")) + } try { yamlWriter.createConfigProps(extension, extension) fail() @@ -298,88 +311,91 @@ class YamlWriterTest { instrumentationApk=build/test/*.apk roboScript=foo roboDirective=[[click, resource_id]] - """.trimIndent() + """.trimIndent(), ) } } @Test fun verifyOnlyWithRoboscriptWorks() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboScript.set("foo") - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + roboScript.set("foo") + } val configProps = yamlWriter.createConfigProps(extension, extension) assertThat(configProps).isEqualTo( """ - gcloud: - app: path - device: - - model: NexusLowRes - version: 28 + gcloud: + app: path + device: + - model: NexusLowRes + version: 28 - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - robo-script: foo - - flank: - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n' + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + robo-script: foo + + flank: + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent() + '\n', ) } @Test fun verifyOnlyWithRobodirectivesWorks() { - val extension = emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboDirectives.add(listOf("click", "resource_id")) - } + val extension = + emptyExtension { + serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) + debugApk.set("path") + roboDirectives.add(listOf("click", "resource_id")) + } val configProps = yamlWriter.createConfigProps(extension, extension) assertThat(configProps).isEqualTo( """ - gcloud: - app: path - device: - - model: NexusLowRes - version: 28 + gcloud: + app: path + device: + - model: NexusLowRes + version: 28 - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - robo-directives: - click:resource_id: "" - - flank: - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n' + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + robo-directives: + click:resource_id: "" + + flank: + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent() + '\n', ) } @Test fun writeNoTestShards() { - val extension = emptyExtension { - } + val extension = + emptyExtension { + } assertEquals( "flank:\n" + @@ -390,15 +406,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeProjectIdOption() { - val extension = emptyExtension { - projectId.set("foo") - } + val extension = + emptyExtension { + projectId.set("foo") + } assertEquals( "flank:\n" + @@ -410,15 +427,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeTestShardOption() { - val extension = emptyExtension { - testShards.set(5) - } + val extension = + emptyExtension { + testShards.set(5) + } assertEquals( "flank:\n" + @@ -430,15 +448,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeShardTimeOption() { - val extension = emptyExtension { - shardTime.set(120) - } + val extension = + emptyExtension { + shardTime.set(120) + } assertEquals( "flank:\n" + @@ -450,7 +469,7 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @@ -467,15 +486,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeTestRepeats() { - val extension = emptyExtension { - repeatTests.set(5) - } + val extension = + emptyExtension { + repeatTests.set(5) + } assertEquals( "flank:\n" + @@ -487,16 +507,17 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeTestShardAndRepeatOption() { - val extension = emptyExtension { - testShards.set(5) - repeatTests.set(2) - } + val extension = + emptyExtension { + testShards.set(5) + repeatTests.set(2) + } assertEquals( "flank:\n" + @@ -509,15 +530,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeResultsHistoryName() { - val extension = emptyExtension { - resultsHistoryName.set("androidtest") - } + val extension = + emptyExtension { + resultsHistoryName.set("androidtest") + } assertEquals( " use-orchestrator: false\n" + @@ -527,15 +549,16 @@ class YamlWriterTest { " timeout: 15m\n" + " results-history-name: androidtest\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeResultsBucket() { - val extension = emptyExtension { - resultsBucket.set("fake-project.appspot.com") - } + val extension = + emptyExtension { + resultsBucket.set("fake-project.appspot.com") + } assertEquals( " use-orchestrator: false\n" + @@ -545,15 +568,16 @@ class YamlWriterTest { " timeout: 15m\n" + " results-bucket: fake-project.appspot.com\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeResultsDir() { - val extension = emptyExtension { - resultsDir.set("resultsGoHere") - } + val extension = + emptyExtension { + resultsDir.set("resultsGoHere") + } assertEquals( " use-orchestrator: false\n" + @@ -563,20 +587,21 @@ class YamlWriterTest { " timeout: 15m\n" + " num-flaky-test-attempts: 0\n" + " results-dir: resultsGoHere\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeTestTargetsAndResultsHistoryName() { - val extension = emptyExtension { - resultsHistoryName.set("androidtest") - testTargets.set( - project.provider { - listOf("class com.example.Foo") - } - ) - } + val extension = + emptyExtension { + resultsHistoryName.set("androidtest") + testTargets.set( + project.provider { + listOf("class com.example.Foo") + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -588,7 +613,7 @@ class YamlWriterTest { " test-targets:\n" + " - class com.example.Foo\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @@ -603,19 +628,20 @@ class YamlWriterTest { " performance-metrics: true\n" + " timeout: 15m\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeSingleTestTargets() { - val extension = emptyExtension { - testTargets.set( - project.provider { - listOf("class com.example.Foo#testThing") - } - ) - } + val extension = + emptyExtension { + testTargets.set( + project.provider { + listOf("class com.example.Foo#testThing") + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -626,19 +652,20 @@ class YamlWriterTest { " test-targets:\n" + " - class com.example.Foo#testThing\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeMultipleTestTargets() { - val extension = emptyExtension { - testTargets.set( - project.provider { - listOf("class com.example.Foo#testThing", "class com.example.Foo#testThing2") - } - ) - } + val extension = + emptyExtension { + testTargets.set( + project.provider { + listOf("class com.example.Foo#testThing", "class com.example.Foo#testThing2") + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -650,15 +677,16 @@ class YamlWriterTest { " - class com.example.Foo#testThing\n" + " - class com.example.Foo#testThing2\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeSmartFlankGcsPath() { - val extension = emptyExtension { - smartFlankGcsPath.set("gs://test/fakepath.xml") - } + val extension = + emptyExtension { + smartFlankGcsPath.set("gs://test/fakepath.xml") + } assertEquals( "flank:\n" + @@ -670,19 +698,20 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeNoDirectoriesToPull() { - val extension = emptyExtension { - directoriesToPull.set( - project.provider { - emptyList() - } - ) - } + val extension = + emptyExtension { + directoriesToPull.set( + project.provider { + emptyList() + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -691,19 +720,20 @@ class YamlWriterTest { " performance-metrics: true\n" + " timeout: 15m\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeSingleDirectoriesToPull() { - val extension = emptyExtension { - directoriesToPull.set( - project.provider { - listOf("/sdcard/screenshots") - } - ) - } + val extension = + emptyExtension { + directoriesToPull.set( + project.provider { + listOf("/sdcard/screenshots") + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -714,19 +744,20 @@ class YamlWriterTest { " directories-to-pull:\n" + " - /sdcard/screenshots\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeMultipleDirectoriesToPull() { - val extension = emptyExtension { - directoriesToPull.set( - project.provider { - listOf("/sdcard/screenshots", "/sdcard/reports") - } - ) - } + val extension = + emptyExtension { + directoriesToPull.set( + project.provider { + listOf("/sdcard/screenshots", "/sdcard/reports") + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -738,19 +769,20 @@ class YamlWriterTest { " - /sdcard/screenshots\n" + " - /sdcard/reports\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeNoFilesToDownload() { - val extension = emptyExtension { - filesToDownload.set( - project.provider { - emptyList() - } - ) - } + val extension = + emptyExtension { + filesToDownload.set( + project.provider { + emptyList() + }, + ) + } assertEquals( "flank:\n" + @@ -761,19 +793,20 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeSingleFilesToDownload() { - val extension = emptyExtension { - filesToDownload.set( - project.provider { - listOf(".*/screenshots/.*") - } - ) - } + val extension = + emptyExtension { + filesToDownload.set( + project.provider { + listOf(".*/screenshots/.*") + }, + ) + } assertEquals( "flank:\n" + @@ -786,19 +819,20 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeMultipleFilesToDownload() { - val extension = emptyExtension { - filesToDownload.set( - project.provider { - listOf(".*/screenshots/.*", ".*/reports/.*") - } - ) - } + val extension = + emptyExtension { + filesToDownload.set( + project.provider { + listOf(".*/screenshots/.*", ".*/reports/.*") + }, + ) + } assertEquals( "flank:\n" + @@ -812,21 +846,22 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeSingleEnvironmentVariables() { - val extension = emptyExtension { - environmentVariables.set( - project.provider { - mapOf( - "listener" to "com.osacky.flank.sample.Listener" - ) - } - ) - } + val extension = + emptyExtension { + environmentVariables.set( + project.provider { + mapOf( + "listener" to "com.osacky.flank.sample.Listener", + ) + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -837,22 +872,23 @@ class YamlWriterTest { " environment-variables:\n" + " listener: com.osacky.flank.sample.Listener\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeMultipleEnvironmentVariables() { - val extension = emptyExtension { - environmentVariables.set( - project.provider { - mapOf( - "clearPackageData" to "true", - "listener" to "com.osacky.flank.sample.Listener" - ) - } - ) - } + val extension = + emptyExtension { + environmentVariables.set( + project.provider { + mapOf( + "clearPackageData" to "true", + "listener" to "com.osacky.flank.sample.Listener", + ) + }, + ) + } assertEquals( " use-orchestrator: false\n" + @@ -864,19 +900,20 @@ class YamlWriterTest { " clearPackageData: true\n" + " listener: com.osacky.flank.sample.Listener\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @Test fun writeDefaultProperties() { - val extension = emptyExtension { - useOrchestrator.set(true) - autoGoogleLogin.set(true) - recordVideo.set(false) - performanceMetrics.set(false) - testTimeout.set("45m") - } + val extension = + emptyExtension { + useOrchestrator.set(true) + autoGoogleLogin.set(true) + recordVideo.set(false) + performanceMetrics.set(false) + testTimeout.set("45m") + } assertEquals( " use-orchestrator: true\n" + @@ -885,7 +922,7 @@ class YamlWriterTest { " performance-metrics: false\n" + " timeout: 45m\n" + " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension) + yamlWriter.writeAdditionalProperties(extension), ) } @@ -902,15 +939,16 @@ class YamlWriterTest { " legacy-junit-result: false\n" + " full-junit-result: false\n" + " output-style: single\n", - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ) } @Test fun writeKeepFilePath() { - val extension = emptyExtension { - keepFilePath.set(true) - } + val extension = + emptyExtension { + keepFilePath.set(true) + } assertThat(yamlWriter.writeFlankProperties(extension)) .isEqualTo( @@ -921,49 +959,50 @@ class YamlWriterTest { " smart-flank-disable-upload: false\n" + " legacy-junit-result: false\n" + " full-junit-result: false\n" + - " output-style: single\n" + " output-style: single\n", ) } @Test fun writeAdditionalTestApks() { - val extension = emptyExtension { - debugApk.set("../orange/build/output/app.apk") - instrumentationApk.set("../orange/build/output/app-test.apk") - additionalTestApks.set( - project.provider { - listOf( - "- app: ../orange/build/output/app.apk", - " test: ../orange/build/output/app-test2.apk", - "- app: ../bob/build/output/app.apk", - " test: ../bob/build/output/app-test.apk", - "- test: ../bob/build/output/app-test2.apk", - "- test: ../bob/build/output/app-test3.apk" - ) - } - ) - } + val extension = + emptyExtension { + debugApk.set("../orange/build/output/app.apk") + instrumentationApk.set("../orange/build/output/app-test.apk") + additionalTestApks.set( + project.provider { + listOf( + "- app: ../orange/build/output/app.apk", + " test: ../orange/build/output/app-test2.apk", + "- app: ../bob/build/output/app.apk", + " test: ../bob/build/output/app-test.apk", + "- test: ../bob/build/output/app-test2.apk", + "- test: ../bob/build/output/app-test3.apk", + ) + }, + ) + } assertThat( - yamlWriter.writeFlankProperties(extension) + yamlWriter.writeFlankProperties(extension), ).isEqualTo( """ - flank: - keep-file-path: false - additional-app-test-apks: - - app: ../orange/build/output/app.apk - test: ../orange/build/output/app-test2.apk - - app: ../bob/build/output/app.apk - test: ../bob/build/output/app-test.apk - - test: ../bob/build/output/app-test2.apk - - test: ../bob/build/output/app-test3.apk - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n' + flank: + keep-file-path: false + additional-app-test-apks: + - app: ../orange/build/output/app.apk + test: ../orange/build/output/app-test2.apk + - app: ../bob/build/output/app.apk + test: ../bob/build/output/app-test.apk + - test: ../bob/build/output/app-test2.apk + - test: ../bob/build/output/app-test3.apk + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent() + '\n', ) } @@ -986,12 +1025,12 @@ class YamlWriterTest { val expectedAdditional = """ - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 """.trimIndent() assertEquals(expectedFlank, defaultFlankProperties) @@ -1000,72 +1039,80 @@ class YamlWriterTest { @Test fun writeRunTimeout() { - val extension = emptyExtension { - runTimeout.set("20m") - } + val extension = + emptyExtension { + runTimeout.set("20m") + } assertTrue(yamlWriter.writeFlankProperties(extension).contains(" run-timeout: 20m")) } @Test fun writeIgnoreFailedTests() { - val properties = emptyExtension { - ignoreFailedTests.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + ignoreFailedTests.set(true) + }.toFlankProperties() assertTrue(properties.contains(" ignore-failed-tests: true")) } @Test fun writeDisableSharding() { - val properties = emptyExtension { - disableSharding.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + disableSharding.set(true) + }.toFlankProperties() assertTrue(properties.contains(" disable-sharding: true")) } @Test fun writeSmartFlankDisableUpload() { - val properties = emptyExtension { - smartFlankDisableUpload.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + smartFlankDisableUpload.set(true) + }.toFlankProperties() assertTrue(properties.contains(" smart-flank-disable-upload: true")) } @Test fun writeTestRunnerClass() { - val properties = emptyExtension { - testRunnerClass.set("any.class.Runner") - }.toAdditionalProperties() + val properties = + emptyExtension { + testRunnerClass.set("any.class.Runner") + }.toAdditionalProperties() assertTrue(properties.contains(" test-runner-class: any.class.Runner")) } @Test fun writeLocalResultsDir() { - val properties = emptyExtension { - localResultsDir.set("~/my/results/dir") - }.toFlankProperties() + val properties = + emptyExtension { + localResultsDir.set("~/my/results/dir") + }.toFlankProperties() assertTrue(properties.contains(" local-result-dir: ~/my/results/dir")) } @Test fun writeNumUniformShards() { - val properties = emptyExtension { - numUniformShards.set(20) - }.toAdditionalProperties() + val properties = + emptyExtension { + numUniformShards.set(20) + }.toAdditionalProperties() assertTrue(properties.contains(" num-uniform-shards: 20")) } @Test fun writeOutputStyle() { - val properties = emptyExtension { - outputStyle.set("anyString") - }.toFlankProperties() + val properties = + emptyExtension { + outputStyle.set("anyString") + }.toFlankProperties() assertTrue(properties.contains(" output-style: anyString")) } @@ -1079,9 +1126,10 @@ class YamlWriterTest { @Test fun writeLegacyJunitResult() { - val properties = emptyExtension { - legacyJunitResult.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + legacyJunitResult.set(true) + }.toFlankProperties() assertTrue(properties.contains(" legacy-junit-result: true")) } @@ -1095,17 +1143,20 @@ class YamlWriterTest { @Test fun writeFullJunitResult() { - val properties = emptyExtension { - fullJunitResult.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + fullJunitResult.set(true) + }.toFlankProperties() assertTrue(properties.contains(" full-junit-result: true")) } + @Test fun writeAsyncFlag() { - val properties = emptyExtension { - async.set(true) - }.toAdditionalProperties() + val properties = + emptyExtension { + async.set(true) + }.toAdditionalProperties() assertTrue(properties.contains(" async: true")) } @@ -1118,16 +1169,17 @@ class YamlWriterTest { @Test fun writeClientDetails() { - val properties = emptyExtension { - clientDetails.set( - project.provider { - mapOf( - "anyDetail1" to "anyValue1", - "anyDetail2" to "anyValue2" - ) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + clientDetails.set( + project.provider { + mapOf( + "anyDetail1" to "anyValue1", + "anyDetail2" to "anyValue2", + ) + }, + ) + }.toAdditionalProperties() assertTrue( properties.contains( @@ -1135,24 +1187,25 @@ class YamlWriterTest { | client-details: | anyDetail1: anyValue1 | anyDetail2: anyValue2 - """.trimMargin() - ) + """.trimMargin(), + ), ) } @Test fun writeTestTargetsAlwaysRun() { - val properties = emptyExtension { - testTargetsAlwaysRun.set( - project.provider { - listOf( - "com.example.FirstTests#test1", - "com.example.FirstTests#test2", - "com.example.FirstTests#test3" - ) - } - ) - }.toFlankProperties() + val properties = + emptyExtension { + testTargetsAlwaysRun.set( + project.provider { + listOf( + "com.example.FirstTests#test1", + "com.example.FirstTests#test2", + "com.example.FirstTests#test3", + ) + }, + ) + }.toFlankProperties() assertTrue( properties.contains( @@ -1161,23 +1214,24 @@ class YamlWriterTest { | - class com.example.FirstTests#test1 | - class com.example.FirstTests#test2 | - class com.example.FirstTests#test3 - """.trimMargin() - ) + """.trimMargin(), + ), ) } @Test fun writeOtherFiles() { - val properties = emptyExtension { - otherFiles.set( - project.provider { - mapOf( - "/example/path/test1" to "anyfile.txt", - "/example/path/test2" to "anyfile2.txt" - ) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + otherFiles.set( + project.provider { + mapOf( + "/example/path/test1" to "anyfile.txt", + "/example/path/test2" to "anyfile2.txt", + ) + }, + ) + }.toAdditionalProperties() assertTrue( properties.contains( @@ -1185,42 +1239,45 @@ class YamlWriterTest { | other-files: | /example/path/test1: anyfile.txt | /example/path/test2: anyfile2.txt - """.trimMargin() - ) + """.trimMargin(), + ), ) } @Test fun writeNetworkProfile() { - val properties = emptyExtension { - networkProfile.set("LTE") - }.toAdditionalProperties() + val properties = + emptyExtension { + networkProfile.set("LTE") + }.toAdditionalProperties() assertTrue(properties.contains(" network-profile: LTE")) } @Test fun writeRoboScript() { - val properties = emptyExtension { - roboScript.set("~/my/dir/with/script.json") - }.toAdditionalProperties() + val properties = + emptyExtension { + roboScript.set("~/my/dir/with/script.json") + }.toAdditionalProperties() assertTrue(properties.contains(" robo-script: ~/my/dir/with/script.json")) } @Test fun writeRoboDirectives() { - val properties = emptyExtension { - roboDirectives.set( - project.provider { - listOf( - listOf("click", "button3"), - listOf("ignore", "button1", ""), - listOf("text", "field1", "my common text") - ) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + roboDirectives.set( + project.provider { + listOf( + listOf("click", "button3"), + listOf("ignore", "button1", ""), + listOf("text", "field1", "my common text"), + ) + }, + ) + }.toAdditionalProperties() assertTrue( properties.contains( @@ -1229,130 +1286,140 @@ class YamlWriterTest { | click:button3: "" | ignore:button1: "" | text:field1: my common text - """.trimMargin() - ) + """.trimMargin(), + ), ) } @Test fun writeDefaultTestTime() { - val properties = emptyExtension { - defaultTestTime.set(240.5) - }.toFlankProperties() + val properties = + emptyExtension { + defaultTestTime.set(240.5) + }.toFlankProperties() assertThat(properties).contains("default-test-time: 240.5") } @Test fun writeDefaultClassTestTime() { - val properties = emptyExtension { - defaultClassTestTime.set(681.8) - }.toFlankProperties() + val properties = + emptyExtension { + defaultClassTestTime.set(681.8) + }.toFlankProperties() assertThat(properties).contains("default-class-test-time: 681.8") } @Test fun writeUseAverageTestTimeForNewTests() { - val properties = emptyExtension { - useAverageTestTimeForNewTests.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + useAverageTestTimeForNewTests.set(true) + }.toFlankProperties() assertThat(properties).contains("use-average-test-time-for-new-tests: true") } @Test fun writeAdditionalApks() { - val properties = emptyExtension { - additionalApks.set( - project.provider { - listOf("gs://path/to/app1.apk", "localPath/to/app2.apk") - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + additionalApks.set( + project.provider { + listOf("gs://path/to/app1.apk", "localPath/to/app2.apk") + }, + ) + }.toAdditionalProperties() assertThat(properties).contains( """ | additional-apks: | - gs://path/to/app1.apk | - localPath/to/app2.apk - """.trimMargin() + """.trimMargin(), ) } @Test fun writeDisableResultsUpload() { - val properties = emptyExtension { - disableResultsUpload.set(true) - }.toFlankProperties() + val properties = + emptyExtension { + disableResultsUpload.set(true) + }.toFlankProperties() assertThat(properties).contains("disable-results-upload: true") } @Test fun writeTestTargetsForShard() { - val properties = emptyExtension { - testTargetsForShard.addAll( - "class com.example.test_app.bar.BarInstrumentedTest", - "class com.example.test_app.foo.FooInstrumentedTest" - ) - }.toFlankProperties() + val properties = + emptyExtension { + testTargetsForShard.addAll( + "class com.example.test_app.bar.BarInstrumentedTest", + "class com.example.test_app.foo.FooInstrumentedTest", + ) + }.toFlankProperties() assertThat(properties).contains( """ | test-targets-for-shard: | - class com.example.test_app.bar.BarInstrumentedTest | - class com.example.test_app.foo.FooInstrumentedTest - """.trimMargin() + """.trimMargin(), ) } @Test fun writeGrantPermissions() { - val properties = emptyExtension { - grantPermissions.set("none") - }.toAdditionalProperties() + val properties = + emptyExtension { + grantPermissions.set("none") + }.toAdditionalProperties() assertThat(properties).contains("grant-permissions: none") } @Test fun writeType() { - val properties = emptyExtension { - type.set("game-loop") - }.toAdditionalProperties() + val properties = + emptyExtension { + type.set("game-loop") + }.toAdditionalProperties() assertThat(properties).contains("type: game-loop") } @Test fun writeScenarioLabels() { - val properties = emptyExtension { - scenarioLabels.set( - project.provider { - listOf("label1", "label2") - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + scenarioLabels.set( + project.provider { + listOf("label1", "label2") + }, + ) + }.toAdditionalProperties() assertThat(properties).contains( """ | scenario-labels: | - label1 | - label2 - """.trimMargin() + """.trimMargin(), ) } @Test fun writeScenarioNumbers() { - val properties = emptyExtension { - scenarioNumbers.set( - project.provider { - listOf(1, 123, 543) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + scenarioNumbers.set( + project.provider { + listOf(1, 123, 543) + }, + ) + }.toAdditionalProperties() assertThat(properties).contains( """ @@ -1360,95 +1427,102 @@ class YamlWriterTest { | - 1 | - 123 | - 543 - """.trimMargin() + """.trimMargin(), ) } @Test fun writeObbFiles() { - val properties = emptyExtension { - obbFiles.set( - project.provider { - listOf( - "local/file/path/test1.obb", - "local/file/path/test2.obb" - ) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + obbFiles.set( + project.provider { + listOf( + "local/file/path/test1.obb", + "local/file/path/test2.obb", + ) + }, + ) + }.toAdditionalProperties() assertThat(properties).contains( """ | obb-files: | - local/file/path/test1.obb | - local/file/path/test2.obb - """.trimMargin() + """.trimMargin(), ) } @Test fun writeObbNames() { - val properties = emptyExtension { - obbNames.set( - project.provider { - listOf( - "patch.0300110.com.example.android.obb", - "patch.0300111.com.example.android.obb" - ) - } - ) - }.toAdditionalProperties() + val properties = + emptyExtension { + obbNames.set( + project.provider { + listOf( + "patch.0300110.com.example.android.obb", + "patch.0300111.com.example.android.obb", + ) + }, + ) + }.toAdditionalProperties() assertThat(properties).contains( """ | obb-names: | - patch.0300110.com.example.android.obb | - patch.0300111.com.example.android.obb - """.trimMargin() + """.trimMargin(), ) } @Test fun writeFailFast() { - val properties = emptyExtension { - failFast.set(true) - }.toAdditionalProperties() + val properties = + emptyExtension { + failFast.set(true) + }.toAdditionalProperties() assertThat(properties).contains("fail-fast: true") } @Test fun writeMaxTestShardOption() { - val properties = emptyExtension { - maxTestShards.set(8) - }.toFlankProperties() + val properties = + emptyExtension { + maxTestShards.set(8) + }.toFlankProperties() assertThat(properties).contains("max-test-shards: 8") } @Test fun writeSingleLineAdditionalFlankProperty() { - val properties = emptyExtension { - additionalFlankOptions.set("new_version_property: test") - }.toFlankProperties() + val properties = + emptyExtension { + additionalFlankOptions.set("new_version_property: test") + }.toFlankProperties() assertThat(properties).contains(" new_version_property: test") } @Test fun writeSingleLineAdditionalGcloudProperty() { - val properties = emptyExtension { - additionalGcloudOptions.set("new_version_property: test") - }.toAdditionalProperties() + val properties = + emptyExtension { + additionalGcloudOptions.set("new_version_property: test") + }.toAdditionalProperties() assertThat(properties).contains(" new_version_property: test") } @Test fun writeMultiLineAdditionalFlankProperies() { - val properties = emptyExtension { - additionalFlankOptions.set("new_version_property: test\nnew_version_property2: test2") - }.toFlankProperties() + val properties = + emptyExtension { + additionalFlankOptions.set("new_version_property: test\nnew_version_property2: test2") + }.toFlankProperties() assertThat(properties).contains(" new_version_property: test") assertThat(properties).contains(" new_version_property2: test2") @@ -1456,16 +1530,20 @@ class YamlWriterTest { @Test fun writeMultiLineAdditionalGcloudProperties() { - val properties = emptyExtension { - additionalGcloudOptions.set("new_version_property: test\nnew_version_property2: test2") - }.toAdditionalProperties() + val properties = + emptyExtension { + additionalGcloudOptions.set("new_version_property: test\nnew_version_property2: test2") + }.toAdditionalProperties() assertThat(properties).contains(" new_version_property: test") assertThat(properties).contains(" new_version_property2: test2") } private fun emptyExtension() = FlankGradleExtension(project.objects) + private fun emptyExtension(block: FlankGradleExtension.() -> Unit) = emptyExtension().apply(block) + private fun FlankGradleExtension.toFlankProperties() = yamlWriter.writeFlankProperties(this).trimIndent() + private fun FlankGradleExtension.toAdditionalProperties() = yamlWriter.writeAdditionalProperties(this) } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt index 869f1fa9..a30daf45 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt @@ -35,7 +35,7 @@ internal fun androidHome(): String { } } throw IllegalStateException( - "Missing 'ANDROID_HOME' environment variable or local.properties with 'sdk.dir'" + "Missing 'ANDROID_HOME' environment variable or local.properties with 'sdk.dir'", ) } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt index e295d2c5..13a82ded 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt @@ -7,9 +7,9 @@ import org.junit.Test import org.junit.rules.TemporaryFolder class AutoConfigureFladleTest { - @get:Rule var testProjectRoot = TemporaryFolder() + fun writeBuildGradle(build: String) { val file = testProjectRoot.newFile("build.gradle") file.writeText(build) @@ -22,63 +22,64 @@ class AutoConfigureFladleTest { testProjectRoot.newFile("gradle.properties").writeText("android.useAndroidX=true") writeBuildGradle( """ - allprojects { - repositories { - google() - mavenCentral() - } - } - """.trimIndent() + allprojects { + repositories { + google() + mavenCentral() + } + } + """.trimIndent(), ) testProjectRoot.newFile("settings.gradle").writeText( """ - include '$fixtureName' - """.trimIndent() + include '$fixtureName' + """.trimIndent(), ) testProjectRoot.setupFixture(fixtureName) - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withArguments("assembleDebug", "assembleDebugAndroidTest", "printYml", "--stacktrace") - .build() + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withArguments("assembleDebug", "assembleDebugAndroidTest", "printYml", "--stacktrace") + .build() assertThat(result.output).contains("BUILD SUCCESSFUL") assertThat(result.output).containsMatch( """ - > Task :android-project:printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + > Task :android-project:printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - listener: com.osacky.flank.sample.Listener - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 + use-orchestrator: true + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + listener: com.osacky.flank.sample.Listener + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView + num-flaky-test-attempts: 0 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt index c8f24e7c..2f9b5582 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt @@ -7,7 +7,6 @@ import org.junit.Test import org.junit.rules.TemporaryFolder class ConfigurationCacheTest { - @get:Rule var testProjectRoot = TemporaryFolder() @@ -22,7 +21,7 @@ class ConfigurationCacheTest { """plugins { | id "com.osacky.fladle" |} - """.trimMargin() + """.trimMargin(), ) val result = configCachingRunner("help").build() @@ -47,7 +46,7 @@ class ConfigurationCacheTest { | localResultsDir = "foo" |} | - """.trimMargin() + """.trimMargin(), ) testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{}") val result = configCachingRunner("writeConfigProps").build() @@ -79,7 +78,7 @@ class ConfigurationCacheTest { | instrumentationApk = "test.apk" |} | - """.trimMargin() + """.trimMargin(), ) testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{}") val result = configCachingRunner("flankDoctor").build() @@ -109,7 +108,7 @@ class ConfigurationCacheTest { | localResultsDir = "foo" |} | - """.trimMargin() + """.trimMargin(), ) val settings = testProjectRoot.newFile("settings.gradle") @@ -118,7 +117,7 @@ class ConfigurationCacheTest { plugins { id 'com.gradle.enterprise' version '3.7' } - """.trimIndent() + """.trimIndent(), ) testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{ \"project_id\": \"foo\" }") val result = configCachingRunner("runFlank").buildAndFail() diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt index 4be914c1..fffb40e9 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt @@ -27,7 +27,7 @@ class FlankAuthTestTask { | timeout.set(Duration.ofSeconds(5)) |} | - """.trimMargin() + """.trimMargin(), ) val result = testProjectRoot.gradleRunner().withArguments("flankAuth").buildAndFail() diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt index 2d7990c6..66f8cd8f 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt @@ -8,12 +8,11 @@ import org.junit.Test import org.junit.rules.TemporaryFolder class FlankGradlePluginIntegrationTest { - @get:Rule var testProjectRoot = TemporaryFolder() val minSupportGradleVersion = "6.5" - val oldVersion = "5.3.1" + val oldVersion = "6.4" fun writeBuildGradle(build: String) { testProjectRoot.writeBuildDotGradle(build) @@ -25,29 +24,31 @@ class FlankGradlePluginIntegrationTest { """plugins { | id "com.osacky.fladle" |} - """.trimMargin() + """.trimMargin(), ) - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(oldVersion) - .buildAndFail() - assertThat(result.output).contains("Fladle requires at minimum version Gradle 5.5. Detected version Gradle 5.3.1") + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withGradleVersion(oldVersion) + .buildAndFail() + assertThat(result.output).contains("Fladle requires at minimum version Gradle 6.5. Detected version Gradle 6.4") } @Test - fun testGradleSixZero() { + fun testGradleSevenOh() { writeBuildGradle( """plugins { | id "com.osacky.fladle" |} - """.trimMargin() + """.trimMargin(), ) - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion("6.0") - .build() + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withGradleVersion("7.0") + .build() assertThat(result.output).contains("SUCCESS") } @@ -58,7 +59,7 @@ class FlankGradlePluginIntegrationTest { """plugins { | id "com.osacky.fladle" |} - """.trimMargin() + """.trimMargin(), ) GradleRunner.create() .withProjectDir(testProjectRoot.root) @@ -79,7 +80,7 @@ class FlankGradlePluginIntegrationTest { | debugApk = "foo" | instrumentationApk = "fakeInstrument.apk" |} - """.trimMargin() + """.trimMargin(), ) GradleRunner.create() .withProjectDir(testProjectRoot.root) @@ -99,15 +100,21 @@ class FlankGradlePluginIntegrationTest { |fladle { | debugApk = "foo" |} - """.trimMargin() + """.trimMargin(), + ) + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withGradleVersion(minSupportGradleVersion) + .withArguments("printYml") + .buildAndFail() + assertThat( + result.output, + ).contains( + "ServiceAccountCredentials in fladle extension not set." + + "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials", ) - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - assertThat(result.output).contains("ServiceAccountCredentials in fladle extension not set. https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials") } @Test @@ -120,37 +127,40 @@ class FlankGradlePluginIntegrationTest { | serviceAccountCredentials = project.layout.projectDirectory.file("foo") |} | - """.trimMargin() + """.trimMargin(), ) testProjectRoot.newFile("foo").writeText("{}") - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("runFlank") - .buildAndFail() + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withGradleVersion(minSupportGradleVersion) + .withArguments("runFlank") + .buildAndFail() assertThat(result.output).contains("debugApk must be specified") } @Test fun testMissingInstrumentationApkFailsBuild() { writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("foo") - debugApk = "test-debug.apk" - } - """.trimIndent() + """ + plugins { + id "com.osacky.fladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("foo") + debugApk = "test-debug.apk" + } + """.trimIndent(), ) testProjectRoot.newFile("foo").writeText("{}") - val result = GradleRunner.create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("runFlank") - .buildAndFail() + val result = + GradleRunner.create() + .withProjectDir(testProjectRoot.root) + .withPluginClasspath() + .withGradleVersion(minSupportGradleVersion) + .withArguments("runFlank") + .buildAndFail() assertThat(result.output).contains("Must specify either a instrumentationApk file or a roboScript file or a robo directive.") } @@ -158,22 +168,24 @@ class FlankGradlePluginIntegrationTest { @Test fun testSpecifyingBothInstrumentationAndRoboscriptFailsBuild() { writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboScript = "foo.script" - } - """.trimIndent() + """ + plugins { + id "com.osacky.fladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") + debugApk = "test-debug.apk" + instrumentationApk = "instrumentation-debug.apk" + roboScript = "foo.script" + } + """.trimIndent(), ) testProjectRoot.writeEmptyServiceCredential() - val result = testProjectRoot.gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withGradleVersion(minSupportGradleVersion) + .withArguments("printYml") + .buildAndFail() assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") } @@ -181,24 +193,26 @@ class FlankGradlePluginIntegrationTest { @Test fun testSpecifyingBothInstrumentationAndRobodirectiveFailsBuild() { writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent() + """ + plugins { + id "com.osacky.fladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") + debugApk = "test-debug.apk" + instrumentationApk = "instrumentation-debug.apk" + roboDirectives = [ + ["click", "resource_id"], + ] + } + """.trimIndent(), ) testProjectRoot.writeEmptyServiceCredential() - val result = testProjectRoot.gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withGradleVersion(minSupportGradleVersion) + .withArguments("printYml") + .buildAndFail() assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") } @@ -206,24 +220,26 @@ class FlankGradlePluginIntegrationTest { @Test fun testSpecifyingBothRoboscriptAndRobodirectiveFailsBuild() { writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - roboScript = "foo.script" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent() + """ + plugins { + id "com.osacky.fladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") + debugApk = "test-debug.apk" + roboScript = "foo.script" + roboDirectives = [ + ["click", "resource_id"], + ] + } + """.trimIndent(), ) testProjectRoot.writeEmptyServiceCredential() - val result = testProjectRoot.gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withGradleVersion(minSupportGradleVersion) + .withArguments("printYml") + .buildAndFail() assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") } @@ -231,25 +247,27 @@ class FlankGradlePluginIntegrationTest { @Test fun testSpecifyingInstrumentationAndRoboscriptAndRobodirectiveFailsBuild() { writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboScript = "foo.script" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent() + """ + plugins { + id "com.osacky.fladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") + debugApk = "test-debug.apk" + instrumentationApk = "instrumentation-debug.apk" + roboScript = "foo.script" + roboDirectives = [ + ["click", "resource_id"], + ] + } + """.trimIndent(), ) testProjectRoot.writeEmptyServiceCredential() - val result = testProjectRoot.gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withGradleVersion(minSupportGradleVersion) + .withArguments("printYml") + .buildAndFail() assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") } @@ -269,13 +287,14 @@ class FlankGradlePluginIntegrationTest { } } } - """.trimMargin() + """.trimMargin(), ) testProjectRoot.writeEmptyServiceCredential() - val result = testProjectRoot.gradleRunner() - .withGradleVersion("7.0-rc-1") - .withArguments("printYmlFooConfig") - .build() + val result = + testProjectRoot.gradleRunner() + .withGradleVersion("7.0-rc-1") + .withArguments("printYmlFooConfig") + .build() assertThat(result.task(":printYmlFooConfig")!!.outcome).isEqualTo(TaskOutcome.SUCCESS) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt index 77f83ec6..de3db3b5 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt @@ -23,11 +23,12 @@ class FulladlePluginIntegrationTest { """plugins { | id "com.osacky.fulladle" |} - """.trimMargin() + """.trimMargin(), ) - val result = testProjectRoot.gradleRunner() - .withArguments("help") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments("help") + .build() assertThat(result.output).contains("SUCCESS") } @@ -38,17 +39,17 @@ class FulladlePluginIntegrationTest { val ignoredLibraryProject = "android-lib-ignored" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$libraryFixture' - include '$ignoredLibraryProject' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + include '$appFixture' + include '$libraryFixture' + include '$ignoredLibraryProject' + + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(libraryFixture) @@ -56,25 +57,25 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + dependencies { + classpath '$agpDependency' + } + } + + plugins { + id "com.osacky.fulladle" + } + + + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) // Configure second included project to ignore fulladle module @@ -83,44 +84,45 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") // Ensure that there is only one additional test APK even though there are two library modules. assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + > Task :printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + + flank: + keep-file-path: false + additional-app-test-apks: + - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk + + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } @@ -131,17 +133,17 @@ class FulladlePluginIntegrationTest { val nonAndroidFixture = "lib1" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$libraryFixture' - include '$nonAndroidFixture' + include '$appFixture' + include '$libraryFixture' + include '$nonAndroidFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(libraryFixture) @@ -149,25 +151,25 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + dependencies { + classpath '$agpDependency' + } + } + + plugins { + id "com.osacky.fulladle" + } + + + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) // Configure second included project to ignore fulladle module @@ -176,19 +178,20 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$nonAndroidFixture/build.gradle").writeText( """ - apply plugin: 'java-library' + apply plugin: 'java-library' - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") } @@ -201,18 +204,18 @@ class FulladlePluginIntegrationTest { val libraryFixture2 = "android-lib2" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' - include '$libraryFixture2' + include '$appFixture' + include '$appFixture2' + include '$libraryFixture' + include '$libraryFixture2' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(appFixture2) @@ -221,28 +224,28 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - environmentVariables = [ - "clearPackageData": "true", - "listener": "com.osacky.flank.sample.Listener" - ] - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + environmentVariables = [ + "clearPackageData": "true", + "listener": "com.osacky.flank.sample.Listener" + ] + } + """.trimIndent(), ) File(testProjectRoot.root, "$libraryFixture2/build.gradle").appendText( @@ -251,7 +254,7 @@ class FulladlePluginIntegrationTest { maxTestShards = 4 clientDetails = ["test-type": "PR","build-number": "132"] } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$libraryFixture/build.gradle").appendText( @@ -264,63 +267,64 @@ class FulladlePluginIntegrationTest { ] debugApk = "dummy_app.apk" } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - listener: com.osacky.flank.sample.Listener - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk - test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk - max-test-shards: 5 - environment-variables: - clearPackageData: false - - - test: [0-9a-zA-Z\/_]*/$libraryFixture2/build/outputs/apk/androidTest/debug/android-lib2-debug-androidTest.apk - max-test-shards: 4 - client-details: - test-type: PR - build-number: 132 - - - app: dummy_app.apk - test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - max-test-shards: 7 - environment-variables: - clearPackageData: false - listener: com.osacky.flank.sample.Listener.Different - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + > Task :printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + listener: com.osacky.flank.sample.Listener + num-flaky-test-attempts: 0 + + flank: + keep-file-path: false + additional-app-test-apks: + - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk + test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk + max-test-shards: 5 + environment-variables: + clearPackageData: false + + - test: [0-9a-zA-Z\/_]*/$libraryFixture2/build/outputs/apk/androidTest/debug/android-lib2-debug-androidTest.apk + max-test-shards: 4 + client-details: + test-type: PR + build-number: 132 + + - app: dummy_app.apk + test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk + max-test-shards: 7 + environment-variables: + clearPackageData: false + listener: com.osacky.flank.sample.Listener.Different + + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } @@ -343,7 +347,7 @@ class FulladlePluginIntegrationTest { google() } } - """.trimIndent() + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(libraryFixture) @@ -356,7 +360,7 @@ class FulladlePluginIntegrationTest { repositories { google() } - + dependencies { classpath '$agpDependency' } @@ -370,7 +374,7 @@ class FulladlePluginIntegrationTest { fladle { serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") } - """.trimIndent() + """.trimIndent(), ) // Configure flavors in project and library @@ -379,7 +383,7 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { variant = "vanillaDebug" } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$flavourLibrary/build.gradle").appendText( @@ -387,48 +391,49 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { variant = "strawberryDebug" } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/vanilla/debug/android-project-flavors-vanilla-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/vanilla/debug/android-project-flavors-vanilla-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/strawberry/debug/android-library-project-flavors-strawberry-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + > Task :printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + + flank: + keep-file-path: false + additional-app-test-apks: + - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/vanilla/debug/android-project-flavors-vanilla-debug.apk + test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/vanilla/debug/android-project-flavors-vanilla-debug-androidTest.apk + + - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk + + - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/strawberry/debug/android-library-project-flavors-strawberry-debug-androidTest.apk + + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } @@ -451,7 +456,7 @@ class FulladlePluginIntegrationTest { google() } } - """.trimIndent() + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(flavourProject) @@ -463,7 +468,7 @@ class FulladlePluginIntegrationTest { repositories { google() } - + dependencies { classpath '$agpDependency' } @@ -477,46 +482,47 @@ class FulladlePluginIntegrationTest { fladle { serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/chocolate/debug/android-project-flavors-chocolate-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/chocolate/debug/android-project-flavors-chocolate-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/lemon/debug/android-library-project-flavors-lemon-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + > Task :printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + + flank: + keep-file-path: false + additional-app-test-apks: + - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/chocolate/debug/android-project-flavors-chocolate-debug.apk + test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/chocolate/debug/android-project-flavors-chocolate-debug-androidTest.apk + + - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/lemon/debug/android-library-project-flavors-lemon-debug-androidTest.apk + + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } @@ -525,39 +531,39 @@ class FulladlePluginIntegrationTest { val appFixture = "android-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' + include '$appFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - maxTestShards = 4 - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + maxTestShards = 4 + } + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture/build.gradle").appendText( @@ -566,12 +572,13 @@ class FulladlePluginIntegrationTest { enabled = true maxTestShards = 7 } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).doesNotContain("max-test-shards: 4") assertThat(result.output).contains("max-test-shards: 7") assertThat(result.output).doesNotContain("additional-app-test-apks") @@ -582,38 +589,38 @@ class FulladlePluginIntegrationTest { val appFixture = "android-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' + include '$appFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture/build.gradle").appendText( @@ -621,12 +628,13 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .buildAndFail() assertThat(result.output).contains("Task :configureFulladle FAILED") assertThat(result.output).contains("All modules were disabled for testing in fulladleModuleConfig or the enabled modules had no tests") @@ -645,17 +653,17 @@ class FulladlePluginIntegrationTest { val libraryFixture = "android-library-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' + include '$appFixture' + include '$appFixture2' + include '$libraryFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(appFixture2) @@ -663,24 +671,24 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture/build.gradle").appendText( @@ -688,7 +696,7 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( @@ -696,19 +704,21 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .buildAndFail() assertThat(result.output).contains("Task :configureFulladle FAILED") assertThat(result.output).contains( - "Library module :android-library-project did not specify a debug apk. Library modules do not generate a debug apk and one needs to be specified in the fulladleModuleConfig block" + "Library module :android-library-project did not specify a debug apk. Library modules do not " + + "generate a debug apk and one needs to be specified in the fulladleModuleConfig block", ) assertThat(result.output).contains( - "This is a required parameter in FTL which remains unused for library modules under test, and you can use a dummy apk here" + "This is a required parameter in FTL which remains unused for library modules under test, and you can use a dummy apk here", ) } @@ -725,17 +735,17 @@ class FulladlePluginIntegrationTest { val libraryFixture = "android-library-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' + include '$appFixture' + include '$appFixture2' + include '$libraryFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(appFixture2) @@ -743,24 +753,24 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture/build.gradle").appendText( @@ -768,7 +778,7 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( @@ -776,28 +786,29 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$libraryFixture/build.gradle").appendText( """ fulladleModuleConfig { debugApk = "dummy_app.apk" } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).doesNotContain("additional-app-test-apks") assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: dummy_app.apk - test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - """.trimIndent() + > Task :printYml + gcloud: + app: dummy_app.apk + test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk + """.trimIndent(), ) assertThat(result.output).contains("SUCCESS") } @@ -815,17 +826,17 @@ class FulladlePluginIntegrationTest { val libraryFixture = "android-library-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' + include '$appFixture' + include '$appFixture2' + include '$libraryFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(appFixture2) @@ -833,24 +844,24 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } + buildscript { + repositories { + google() + } - dependencies { - classpath '$agpDependency' - } - } + dependencies { + classpath '$agpDependency' + } + } - plugins { - id "com.osacky.fulladle" - } + plugins { + id "com.osacky.fulladle" + } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent() + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + } + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture/build.gradle").appendText( @@ -858,7 +869,7 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = true } - """.trimIndent() + """.trimIndent(), ) File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( @@ -866,12 +877,13 @@ class FulladlePluginIntegrationTest { fulladleModuleConfig { enabled = false } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("BUILD SUCCESSFUL") } @@ -884,18 +896,18 @@ class FulladlePluginIntegrationTest { val libraryFixture = "android-library-project" testProjectRoot.newFile("settings.gradle").writeText( """ - include '$appFixtureWithAbiSplits' - include '$appFixture' - include '$appFixtureTwo' - include '$libraryFixture' + include '$appFixtureWithAbiSplits' + include '$appFixture' + include '$appFixtureTwo' + include '$libraryFixture' - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } + dependencyResolutionManagement { + repositories { + mavenCentral() + google() } - """.trimIndent() + } + """.trimIndent(), ) testProjectRoot.setupFixture(appFixture) testProjectRoot.setupFixture(appFixtureTwo) @@ -904,22 +916,22 @@ class FulladlePluginIntegrationTest { writeBuildGradle( """ - buildscript { - repositories { - google() - } - dependencies { - classpath '$agpDependency' - } - } - plugins { - id "com.osacky.fulladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - abi = "armeabi-v7a" - } - """.trimIndent() + buildscript { + repositories { + google() + } + dependencies { + classpath '$agpDependency' + } + } + plugins { + id "com.osacky.fulladle" + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") + abi = "armeabi-v7a" + } + """.trimIndent(), ) // Overwrite android-project fixture to include ABI splits. @@ -929,11 +941,12 @@ class FulladlePluginIntegrationTest { id "com.android.application" } android { - compileSdkVersion 29 + compileSdk 33 + namespace "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -950,12 +963,13 @@ class FulladlePluginIntegrationTest { } } } - """.trimIndent() + """.trimIndent(), ) - val result = testProjectRoot.gradleRunner() - .withArguments(":printYml") - .build() + val result = + testProjectRoot.gradleRunner() + .withArguments(":printYml") + .build() assertThat(result.output).contains("SUCCESS") // Ensure that: @@ -963,42 +977,42 @@ class FulladlePluginIntegrationTest { // - Any application modules that don't use ABI splits are still present in additional-app-test-apks. assertThat(result.output).containsMatch( """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: NexusLowRes - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/debug/android-project-with-abi-splits-armeabi-v7a-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/androidTest/debug/android-project-with-abi-splits-debug-androidTest.apk - - - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk - test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk - max-test-shards: 5 - environment-variables: - clearPackageData: false - - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + > Task :printYml + gcloud: + app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk + test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk + device: + - model: NexusLowRes + version: 28 + + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + num-flaky-test-attempts: 0 + + flank: + keep-file-path: false + additional-app-test-apks: + - app: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/debug/android-project-with-abi-splits-armeabi-v7a-debug.apk + test: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/androidTest/debug/android-project-with-abi-splits-debug-androidTest.apk + + - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk + test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk + max-test-shards: 5 + environment-variables: + clearPackageData: false + + - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk + + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt index 93b997c8..b3a4767f 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt @@ -7,7 +7,12 @@ import org.junit.Test import org.junit.rules.TemporaryFolder private fun baseConfigMessage(option: String) = "Incorrect [base] configuration. [$option] can't be used together with sanityRobo." -private fun additionalConfigMessage(option: String, name: String) = "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" + +private fun additionalConfigMessage( + option: String, + name: String, +) = "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. " + + "To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" class SanityRoboTest { @get:Rule @@ -35,7 +40,7 @@ class SanityRoboTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -63,7 +68,7 @@ class SanityRoboTest { | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") | roboScript = "some/path/script.json" |} - """.trimMargin() + """.trimMargin(), ) val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() @@ -76,7 +81,7 @@ class SanityRoboTest { fun `sanityRobo - should throw an error if roboDirectives set`() { testProjectRoot.writeBuildDotGradle( buildScript = - """ + """ |plugins { | id "com.osacky.fladle" |} @@ -91,7 +96,7 @@ class SanityRoboTest { | ["text", "field1", "my text"], | ] |} - """.trimMargin() + """.trimMargin(), ) val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() @@ -104,7 +109,7 @@ class SanityRoboTest { fun `sanityRobo - should throw an error if additionalTestApks set`() { testProjectRoot.writeBuildDotGradle( buildScript = - """ + """ |plugins { | id "com.osacky.fladle" |} @@ -119,7 +124,7 @@ class SanityRoboTest { | "- test: test3.apk" | ] |} - """.trimMargin() + """.trimMargin(), ) val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() @@ -152,7 +157,7 @@ class SanityRoboTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val expectedMessage = additionalConfigMessage("roboScript", "sanity") @@ -191,7 +196,7 @@ class SanityRoboTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -226,7 +231,7 @@ class SanityRoboTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) val resultOrange = runner.withArguments("printYmlOrange").build() @@ -255,7 +260,7 @@ class SanityRoboTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) } @@ -283,7 +288,7 @@ class SanityRoboTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -313,7 +318,7 @@ class SanityRoboTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) val resultOrange = runner.withArguments("printYmlOrange").build() @@ -347,7 +352,7 @@ class SanityRoboTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt index 40879192..3c0c27f5 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt @@ -6,18 +6,19 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -private const val commonScriptPart = """ +private const val COMMON_SCRIPT_PART = """ plugins { id 'com.android.application' id 'com.osacky.fladle' } android { - compileSdkVersion 29 + compileSdk 33 + namespace "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -29,7 +30,6 @@ private const val commonScriptPart = """ """ class SanityWithAutoConfigureTest { - @get:Rule var testProjectRoot = TemporaryFolder() @@ -44,7 +44,7 @@ class SanityWithAutoConfigureTest { fun `test auto configuration with sanityRobo set (inner config)`() { testProjectRoot.writeBuildDotGradle( """ - $commonScriptPart + $COMMON_SCRIPT_PART fladle { serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") @@ -78,7 +78,7 @@ class SanityWithAutoConfigureTest { } } } - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -87,36 +87,36 @@ class SanityWithAutoConfigureTest { assertThat(baseResult.output).contains("BUILD SUCCESSFUL") assertThat(baseResult.output).containsMatch( """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + gcloud: + app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk + test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 + use-orchestrator: true + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView + num-flaky-test-attempts: 0 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) val sanityResult = runner.withArguments("printYmlSanity").build() @@ -124,35 +124,35 @@ class SanityWithAutoConfigureTest { assertThat(sanityResult.output).contains("BUILD SUCCESSFUL") assertThat(sanityResult.output).containsMatch( """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + gcloud: + app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 3 + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail + num-flaky-test-attempts: 3 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) val orangesResult = runner.withArguments("printYmlOranges").build() @@ -160,36 +160,36 @@ class SanityWithAutoConfigureTest { assertThat(orangesResult.output).contains("BUILD SUCCESSFUL") assertThat(orangesResult.output).containsMatch( """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + gcloud: + app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk + test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 6 + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail + num-flaky-test-attempts: 6 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } @@ -197,7 +197,7 @@ class SanityWithAutoConfigureTest { fun `test auto configuration with sanityRobo set (base config)`() { testProjectRoot.writeBuildDotGradle( """ - $commonScriptPart + $COMMON_SCRIPT_PART fladle { sanityRobo = true @@ -226,7 +226,7 @@ class SanityWithAutoConfigureTest { } } } - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -235,35 +235,35 @@ class SanityWithAutoConfigureTest { assertThat(baseResult.output).contains("BUILD SUCCESSFUL") assertThat(baseResult.output).containsMatch( """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + gcloud: + app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 + use-orchestrator: true + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView + num-flaky-test-attempts: 0 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) val orangesResult = runner.withArguments("printYmlOranges").build() @@ -271,36 +271,36 @@ class SanityWithAutoConfigureTest { assertThat(orangesResult.output).contains("BUILD SUCCESSFUL") assertThat(orangesResult.output).containsMatch( """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: instrumentation-apk-not-detected-from-root.apk - device: - - model: Pixel2 - version: 26 - - model: Nexus5 - version: 23 + gcloud: + app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk + test: instrumentation-apk-not-detected-from-root.apk + device: + - model: Pixel2 + version: 26 + - model: Nexus5 + version: 23 - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 3 + use-orchestrator: false + auto-google-login: false + record-video: true + performance-metrics: true + timeout: 15m + environment-variables: + clearPackageData: true + test-targets: + - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail + num-flaky-test-attempts: 3 - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + flank: + smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml + keep-file-path: false + ignore-failed-tests: false + disable-sharding: false + smart-flank-disable-upload: false + legacy-junit-result: false + full-junit-result: false + output-style: single + """.trimIndent(), ) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt index 408f95c7..c0f9a0c5 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt @@ -5,9 +5,9 @@ import org.gradle.testkit.runner.BuildResult import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import java.io.File class VariantTests { - @get:Rule var testProjectRoot = TemporaryFolder() @@ -48,25 +48,27 @@ class VariantTests { @Test fun testAdditionalFladleConfigForVariant() { - testProjectRoot.newFile("settings.gradle").writeText("rootProject.name = 'chocovanilla'") - val result = setUpDependOnAssemble( - dependsOnAssemble = true, withFlavors = true, - withFladleConfig = """ - configs { - vanilla { - variant.set("vanillaDebug") + val result = + setUpDependOnAssemble( + dependsOnAssemble = true, + withFlavors = true, + withFladleConfig = + """ + configs { + vanilla { + variant.set("vanillaDebug") + } } - } - """.trimIndent(), - withTask = "runFlankVanilla" - ) + """.trimIndent(), + withTask = "runFlankVanilla", + ) assertThat(result.output).contains(":assembleVanillaDebug") assertThat(result.output).contains(":assembleVanillaDebugAndroidTest") assertThat(result.output).doesNotContain(":assembleVanillaRelease") assertThat(result.output).doesNotContain(":assembleChocolate") - // See #60 https://github.com/runningcode/fladle/issues/60 /** + * See #60 https://github.com/runningcode/fladle/issues/60 testProjectRoot.writeEmptyServiceCredential() val resultPrint = testProjectRoot.gradleRunner() .withArguments("printYmlVanilla") @@ -99,68 +101,90 @@ class VariantTests { withTask: String = "runFlank", dryRun: Boolean = true, ): BuildResult { + testProjectRoot.newFile("settings.gradle").writeText( + """rootProject.name = 'chocovanilla' + |include ':android-project' + """.trimMargin(), + ) testProjectRoot.setupFixture("android-project") - testProjectRoot.writeEmptyServiceCredential() - val flavors = if (withFlavors) { - """ - flavorDimensions "flavor" - productFlavors { - chocolate { - dimension "flavor" - } - vanilla { - dimension "flavor" - } - } - """.trimIndent() - } else { "" } - val abiSplits = if (withAbiSplit) { - """ - splits { - abi { - enable true - reset() - include "x86", "x86_64" - universalApk false - } + val flavors = + if (withFlavors) { + """ + flavorDimensions "flavor" + productFlavors { + chocolate { + dimension "flavor" + } + vanilla { + dimension "flavor" + } + } + """.trimIndent() + } else { + "" + } + val abiSplits = + if (withAbiSplit) { + """ + splits { + abi { + enable true + reset() + include "x86", "x86_64" + universalApk false + } + } + """.trimIndent() + } else { + "" + } + val variant = + if (withFlavors) { + """variant = "chocolateDebug"""" + } else { + "" + } + val abi = + if (withAbiSplit) { + "abi = \"x86\"" + } else { + "" } - """.trimIndent() - } else "" - val variant = if (withFlavors) { """variant = "chocolateDebug"""" } else { "" } - val abi = if (withAbiSplit) { "abi = \"x86\"" } else "" writeBuildGradle( - """plugins { - id "com.osacky.fladle" - id "com.android.application" - } - repositories { - google() - mavenCentral() + """ + plugins { + id "com.osacky.fladle" + id "com.android.application" + } + repositories { + google() + mavenCentral() + } + android { + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" + defaultConfig { + applicationId "com.osacky.flank.gradle.sample" + minSdk 23 + targetSdk 33 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - android { - compileSdkVersion 29 - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - $flavors - $abiSplits - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - dependOnAssemble = $dependsOnAssemble - $variant - $abi - $withFladleConfig - } - """.trimIndent() + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } + $flavors + $abiSplits + } + fladle { + serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") + dependOnAssemble = $dependsOnAssemble + $variant + $abi + $withFladleConfig + } + """.trimIndent(), ) val arguments = mutableListOf(withTask) @@ -173,7 +197,8 @@ class VariantTests { } private fun writeBuildGradle(build: String) { - val file = testProjectRoot.newFile("build.gradle") + // Overwrite existing build.gradle file in "android-project" directory with new text + val file = File(testProjectRoot.root, "android-project/build.gradle") file.writeText(build) } } diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt index fc019bf7..0b5d05b4 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt @@ -28,12 +28,13 @@ class ValidateExclusionsTest { | maxTestShards.set(2) | flankVersion.set("21.01.0") |} - """.trimMargin() + """.trimMargin(), ) - val result = testProjectRoot.gradleRunner() - .withArguments("printYml") - .buildAndFail() + val result = + testProjectRoot.gradleRunner() + .withArguments("printYml") + .buildAndFail() assertThat(result.output).contains("FAILED") assertThat(result.output).contains("Options testShards and maxTestShards cannot be used together. Choose one of them.") @@ -59,7 +60,7 @@ class ValidateExclusionsTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) testProjectRoot.writeEmptyServiceCredential() diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt index d698da67..cde95f54 100644 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt +++ b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt @@ -30,7 +30,9 @@ class ValidateOptionsTest { config.useAverageTestTimeForNewTests.set(true) assertThrows(IllegalStateException::class.java) { validateOptionsUsed(config, "20.05.0") }.run { - assertThat(message).containsMatch("Option useAverageTestTimeForNewTests is available since flank 20.8.4, which is higher than used 20.5.0") + assertThat( + message, + ).containsMatch("Option useAverageTestTimeForNewTests is available since flank 20.8.4, which is higher than used 20.5.0") } } @@ -53,7 +55,9 @@ class ValidateOptionsTest { try { validateOptionsUsed(config, "20.09.10") } catch (e: IllegalStateException) { - assertThat(e).hasMessageThat().contains("Option testTargetsForShard is available since flank 20.12.0, which is higher than used 20.9.10") + assertThat( + e, + ).hasMessageThat().contains("Option testTargetsForShard is available since flank 20.12.0, which is higher than used 20.9.10") } } @@ -79,7 +83,7 @@ class ValidateOptionsTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -117,7 +121,7 @@ class ValidateOptionsTest { | } | } |} - """.trimMargin() + """.trimMargin(), ) val runner = testProjectRoot.gradleRunner() @@ -148,7 +152,7 @@ class ValidateOptionsTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) val resultOrange = runner.withArguments("printYmlNoRecord").build() @@ -178,7 +182,7 @@ class ValidateOptionsTest { | legacy-junit-result: false | full-junit-result: false | output-style: single - """.trimMargin() + """.trimMargin(), ) } } diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle b/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle index 6d646bbe..73bcc57c 100644 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle +++ b/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle @@ -3,9 +3,10 @@ plugins { } android { - compileSdkVersion 29 + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { - targetSdkVersion 29 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -26,11 +27,11 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml index b4f20408..2d886939 100644 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml +++ b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ - - + diff --git a/fladle-plugin/src/test/resources/android-library-project/build.gradle b/fladle-plugin/src/test/resources/android-library-project/build.gradle index 6fc1bd08..7890fc20 100644 --- a/fladle-plugin/src/test/resources/android-library-project/build.gradle +++ b/fladle-plugin/src/test/resources/android-library-project/build.gradle @@ -3,9 +3,10 @@ plugins { } android { - compileSdkVersion 29 + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { - targetSdkVersion 29 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -16,12 +17,12 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml index b4f20408..2d886939 100644 --- a/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml +++ b/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ - - + diff --git a/fladle-plugin/src/test/resources/android-project-flavors/build.gradle b/fladle-plugin/src/test/resources/android-project-flavors/build.gradle index 7a06d197..f9e08150 100644 --- a/fladle-plugin/src/test/resources/android-project-flavors/build.gradle +++ b/fladle-plugin/src/test/resources/android-project-flavors/build.gradle @@ -4,11 +4,12 @@ plugins { } android { - compileSdkVersion 29 + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -16,7 +17,7 @@ android { testOptions { execution 'ANDROIDX_TEST_ORCHESTRATOR' } - flavorDimensions("flavor") + flavorDimensions += "flavor" productFlavors { create("chocolate") { @@ -29,12 +30,12 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml index b4f20408..2d886939 100644 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml +++ b/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ - - + diff --git a/fladle-plugin/src/test/resources/android-project/build.gradle b/fladle-plugin/src/test/resources/android-project/build.gradle index 7da86ca4..3d4ab993 100644 --- a/fladle-plugin/src/test/resources/android-project/build.gradle +++ b/fladle-plugin/src/test/resources/android-project/build.gradle @@ -4,11 +4,12 @@ plugins { } android { - compileSdkVersion 29 + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -45,12 +46,12 @@ fladle { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml index b4f20408..2d886939 100644 --- a/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml +++ b/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ - - + diff --git a/fladle-plugin/src/test/resources/android-project2/build.gradle b/fladle-plugin/src/test/resources/android-project2/build.gradle index 0dff4d89..9a1bc5bc 100644 --- a/fladle-plugin/src/test/resources/android-project2/build.gradle +++ b/fladle-plugin/src/test/resources/android-project2/build.gradle @@ -4,11 +4,12 @@ plugins { } android { - compileSdkVersion 29 + compileSdk 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -48,12 +49,12 @@ fulladleModuleConfig { environmentVariables = ["clearPackageData": "false"] } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml index b4f20408..2d886939 100644 --- a/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml +++ b/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ - - + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 965d0fba..f043dca4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ben-manes-versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } -kotlinter = { id = "org.jmailen.kotlinter", version = "3.9.0" } +kotlinter = { id = "org.jmailen.kotlinter", version = "4.0.0" } gradle-plugin-publish = {id = "com.gradle.plugin-publish", version = "1.2.1" } @@ -13,7 +13,7 @@ agp = { id = "com.android.application", version.ref = "agp-version"} [versions] -androidx-appcompat = "1.2.0" +androidx-appcompat = "1.6.1" androidx-constraintlayout = "1.1.3" @@ -21,14 +21,14 @@ androidx-navigation = "2.3.0" androidx-test-espresso = "3.5.1" -androidx-test-ext-junit = "1.1.1" +androidx-test-ext-junit = "1.1.5" androidx-test-rules = "1.5.0" junit-version = "4.13.2" -kotlin = "1.8.10" -agp-version = "4.2.2" +kotlin = "1.9.10" +agp-version = "7.4.0" flank-version = "23.10.1" [libraries] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index afba109285af78dbd2a1d187e33ac4f87c76e392..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 41204 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 delta 39055 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 /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/sample-android-library/build.gradle.kts b/sample-android-library/build.gradle.kts index 9e190848..751c6fca 100644 --- a/sample-android-library/build.gradle.kts +++ b/sample-android-library/build.gradle.kts @@ -17,13 +17,23 @@ fulladleModuleConfig { } android { - compileSdkVersion(29) + namespace = "com.osacky.flank.gradle.sample" + compileSdk = 33 defaultConfig { - minSdkVersion(23) - targetSdkVersion(29) + minSdk = 23 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } dependencies { diff --git a/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt index 639f7df8..ab896993 100644 --- a/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ b/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt @@ -6,7 +6,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Test fun seeView() { assert(true) diff --git a/sample-android-library/src/main/AndroidManifest.xml b/sample-android-library/src/main/AndroidManifest.xml index 8487ce01..8072ee00 100644 --- a/sample-android-library/src/main/AndroidManifest.xml +++ b/sample-android-library/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt index c0132658..964eb395 100644 --- a/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ b/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } diff --git a/sample-flavors-kotlin/build.gradle.kts b/sample-flavors-kotlin/build.gradle.kts index 9e9afadf..f4c1932b 100644 --- a/sample-flavors-kotlin/build.gradle.kts +++ b/sample-flavors-kotlin/build.gradle.kts @@ -5,39 +5,49 @@ plugins { } android { - compileSdkVersion(29) - defaultConfig { - applicationId = "com.osacky.flank.gradle.sample.kotlin" - minSdkVersion(23) - targetSdkVersion(29) - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" - flavorDimensions("flavor") + namespace = "com.osacky.flank.gradle.sample.kotlin" + compileSdk = 33 + defaultConfig { + applicationId = "com.osacky.flank.gradle.sample.kotlin" + minSdk = 23 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } - productFlavors { - create("chocolate") { - dimension = "flavor" - } - create("vanilla") { - dimension = "flavor" - } - } + testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" + flavorDimensions += "flavor" + productFlavors { + create("chocolate") { + dimension = "flavor" + } + create("vanilla") { + dimension = "flavor" + } + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } androidComponents { - beforeVariants(selector().withName("vanilla")) { variantBuilder -> - variantBuilder.enabled = false - } + beforeVariants(selector().withName("vanilla")) { variantBuilder -> + variantBuilder.enable = false + } } fladle { flankVersion.set("23.10.1") variant.set("chocolateDebug") - debugApk.set(project.provider { "${buildDir.toString()}/outputs/apk/chocolate/debug/*.apk" }) + debugApk.set(provider { layout.buildDirectory.file("/outputs/apk/chocolate/debug/*.apk").get().toString()}) serviceAccountCredentials.set(project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json")) // Project Id is not needed if serviceAccountCredentials are set. // projectId("flank-gradle") diff --git a/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt index 22f874dd..5d25f62c 100644 --- a/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ b/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt @@ -14,7 +14,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Rule @JvmField val testRule = ActivityTestRule(MainActivity::class.java) diff --git a/sample-flavors-kotlin/src/main/AndroidManifest.xml b/sample-flavors-kotlin/src/main/AndroidManifest.xml index c98c1b7f..12058c6f 100644 --- a/sample-flavors-kotlin/src/main/AndroidManifest.xml +++ b/sample-flavors-kotlin/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - - + diff --git a/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt b/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt index 5eb2507a..90fce2f0 100644 --- a/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt +++ b/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) diff --git a/sample-kotlin/build.gradle.kts b/sample-kotlin/build.gradle.kts index de7b775d..93cc9d16 100644 --- a/sample-kotlin/build.gradle.kts +++ b/sample-kotlin/build.gradle.kts @@ -5,16 +5,26 @@ plugins { } android { - compileSdkVersion(29) - defaultConfig { - applicationId = "com.osacky.flank.gradle.sample.kotlin" - minSdkVersion(23) - targetSdkVersion(29) - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" + namespace = "com.osacky.flank.gradle.sample.kotlin" + compileSdk = 33 + defaultConfig { + applicationId = "com.osacky.flank.gradle.sample.kotlin" + minSdk = 23 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } fladle { diff --git a/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt index 90a4828c..57d1a72f 100644 --- a/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ b/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt @@ -13,7 +13,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Rule @JvmField val testRule = ActivityTestRule(MainActivity::class.java) diff --git a/sample-kotlin/src/main/AndroidManifest.xml b/sample-kotlin/src/main/AndroidManifest.xml index d527e8d3..4e2143cd 100644 --- a/sample-kotlin/src/main/AndroidManifest.xml +++ b/sample-kotlin/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - - + diff --git a/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt index d96b3e25..781d1f01 100644 --- a/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ b/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt @@ -5,7 +5,6 @@ import androidx.appcompat.app.AppCompatActivity import com.osacky.flank.gradle.sample.kotlin.R class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) diff --git a/sample/build.gradle b/sample/build.gradle index 6a894ec2..19dc3426 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,11 +3,12 @@ apply plugin: 'kotlin-android' apply plugin: 'com.osacky.fladle' android { - compileSdkVersion 29 + compileSdk = 33 + namespace = "com.osacky.flank.gradle.sample" defaultConfig { applicationId "com.osacky.flank.gradle.sample" - minSdkVersion 23 - targetSdkVersion 29 + minSdk 23 + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -15,6 +16,16 @@ android { testOptions { execution 'ANDROIDX_TEST_ORCHESTRATOR' } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } fladle { diff --git a/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt index bd33fa61..2587b288 100644 --- a/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ b/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt @@ -13,7 +13,6 @@ import java.lang.RuntimeException @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Rule @JvmField val testRule = ActivityTestRule(MainActivity::class.java) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index b6767e24..95244300 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - - + diff --git a/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt index 35264e91..bed8adcc 100644 --- a/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ b/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)