From c07e006ef6e98ba3b1ce160094d0c29df40bf1dc Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 21 Feb 2024 18:01:28 +0100 Subject: [PATCH 1/5] tests: add unit test for feature annotation processing --- libs/processors/build.gradle | 1 + .../processor/RemoteConfigProcessorTest.kt | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt diff --git a/libs/processors/build.gradle b/libs/processors/build.gradle index 8e338bd2ea76..a98f3bd76530 100644 --- a/libs/processors/build.gradle +++ b/libs/processors/build.gradle @@ -14,6 +14,7 @@ dependencies { kapt "com.google.auto.service:auto-service:$googleAutoServiceVersion" implementation "com.squareup:kotlinpoet:$squareupKotlinPoetVersion" + testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.5.0" testImplementation "junit:junit:$junitVersion" testImplementation "org.assertj:assertj-core:$assertjVersion" } diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt new file mode 100644 index 000000000000..8ffb5ea40ff8 --- /dev/null +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -0,0 +1,49 @@ +package org.wordpress.android.processor + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class RemoteConfigProcessorTest { + + @Test + fun `given a class with features annotation, when compiling, generate expected configuration check`() { + // given + val featureA = SourceFile.kotlin( + "Feature.kt", """ + import org.wordpress.android.annotation.Feature + import org.wordpress.android.util.config.AppConfig + + @Feature("remoteField", false) + class A(appConfig: AppConfig, val remoteField: String ="foo") + """ + ) + + // when + val result = compile(listOf(featureA)) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + assertThat(result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigCheck")) + .hasDeclaredMethods("checkRemoteFields") + } + + private fun compile(src: List) = KotlinCompilation().apply { + sources = src + fakeAppConfig + annotationProcessors = listOf(RemoteConfigProcessor()) + inheritClassPath = true + messageOutputStream = System.out + }.compile() + + // Fake AppConfig is needed, as it's a class that is expected to be present in the classpath. Originally, this class + // is placed in `WordPress` module. + private val fakeAppConfig = SourceFile.kotlin( + "AppConfig.kt", """ + package org.wordpress.android.util.config + + class AppConfig + """ + ) + +} From 3876f2b549b4fe9fc5ac33dd66b4417457d31c1d Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 21 Feb 2024 16:15:39 +0100 Subject: [PATCH 2/5] tests: add a test for remote field annotation (defaults generator) --- libs/processors/build.gradle | 1 + .../processor/RemoteConfigProcessorTest.kt | 51 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/libs/processors/build.gradle b/libs/processors/build.gradle index a98f3bd76530..77eb4965cfe2 100644 --- a/libs/processors/build.gradle +++ b/libs/processors/build.gradle @@ -17,4 +17,5 @@ dependencies { testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.5.0" testImplementation "junit:junit:$junitVersion" testImplementation "org.assertj:assertj-core:$assertjVersion" + testImplementation "org.jetbrains.kotlin:kotlin-reflect:$gradle.ext.kotlinVersion" } diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt index 8ffb5ea40ff8..c60e078a1d1c 100644 --- a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -3,30 +3,55 @@ package org.wordpress.android.processor import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.kotlin.utils.addToStdlib.cast import org.junit.Test class RemoteConfigProcessorTest { @Test fun `given a class with features annotation, when compiling, generate expected configuration check`() { + // when + val result = compile(listOf(featureA)) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + assertThat(result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigCheck")) + .hasDeclaredMethods("checkRemoteFields") + } + + @Test + fun `given a class with remote field annotation, when compiling, generate expected config defaults class`() { // given - val featureA = SourceFile.kotlin( - "Feature.kt", """ - import org.wordpress.android.annotation.Feature + val remoteFieldA = SourceFile.kotlin( + "RemoteField.kt", """ + import org.wordpress.android.annotation.RemoteFieldDefaultGenerater import org.wordpress.android.util.config.AppConfig - @Feature("remoteField", false) - class A(appConfig: AppConfig, val remoteField: String ="foo") + @RemoteFieldDefaultGenerater(remoteField = "remoteField", defaultValue = "default") + class RemoteFieldA """ ) // when - val result = compile(listOf(featureA)) + val result = compile( + listOf( + remoteFieldA, + featureA, /* adding a feature, as without it, annotation processor won't start */ + ) + ) // then assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) - assertThat(result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigCheck")) - .hasDeclaredMethods("checkRemoteFields") + val remoteFieldConfigDefaultsClass = + result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFieldConfigDefaults") + val remoteFieldConfigDefaultsObject = remoteFieldConfigDefaultsClass.kotlin.objectInstance + + assertThat( + remoteFieldConfigDefaultsClass.getDeclaredField("remoteFieldConfigDefaults") + .apply { isAccessible = true } + .get(remoteFieldConfigDefaultsObject) + .cast>() + ).containsEntry("remoteField", "default") } private fun compile(src: List) = KotlinCompilation().apply { @@ -46,4 +71,14 @@ class RemoteConfigProcessorTest { """ ) + private val featureA = SourceFile.kotlin( + "Feature.kt", """ + import org.wordpress.android.annotation.Feature + import org.wordpress.android.util.config.AppConfig + + @Feature("remoteField", false) + class FeatureA(appConfig: AppConfig, val remoteField: String ="foo") + """ + ) + } From 71a71d68f528ae66a9992eba9260f42244b42e9a Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 21 Feb 2024 16:24:50 +0100 Subject: [PATCH 3/5] tests: add a test for features and experiments (defaults generator) --- .../processor/RemoteConfigProcessorTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt index c60e078a1d1c..f99c2e3aacc6 100644 --- a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -54,6 +54,41 @@ class RemoteConfigProcessorTest { ).containsEntry("remoteField", "default") } + @Test + fun `given class with feature and class with experiment annotation, when compiling, generate expected config defaults class`() { + // given + val experiment = SourceFile.kotlin( + "Experiment.kt", """ + import org.wordpress.android.annotation.Experiment + import org.wordpress.android.util.config.AppConfig + + @Experiment("experimentFeature", "defaultVariant") + class Experiment + """ + ) + + // when + val result = compile(listOf(featureA, experiment)) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + val remoteFieldConfigDefaultsClass = + result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigDefaults") + val remoteFieldConfigDefaultsObject = remoteFieldConfigDefaultsClass.kotlin.objectInstance + + assertThat( + remoteFieldConfigDefaultsClass.getDeclaredField("remoteFeatureConfigDefaults") + .apply { isAccessible = true } + .get(remoteFieldConfigDefaultsObject) + .cast>() + ).containsExactlyInAnyOrderEntriesOf( + mapOf( + "experimentFeature" to "defaultVariant", + "remoteField" to "false" + ) + ) + } + private fun compile(src: List) = KotlinCompilation().apply { sources = src + fakeAppConfig annotationProcessors = listOf(RemoteConfigProcessor()) From 8586e0fc3f7403a20db959d7722215e919cdc656 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 21 Feb 2024 17:55:52 +0100 Subject: [PATCH 4/5] tests: add a test for features in development generation --- .../processor/RemoteConfigProcessorTest.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt index f99c2e3aacc6..b6ab50d085fe 100644 --- a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -89,6 +89,40 @@ class RemoteConfigProcessorTest { ) } + @Test + fun `given class with feature in development annotation, when compiling, generate expected list of classes`() { + // given + val experiment = SourceFile.kotlin( + "Experiment.kt", """ + import org.wordpress.android.annotation.FeatureInDevelopment + import org.wordpress.android.util.config.AppConfig + + @FeatureInDevelopment + class DevFeature + """ + ) + + // when + val result = compile( + listOf( + experiment, + featureA, /* adding a feature, as without it, annotation processor won't start */ + ) + ) + + // then + + val featuresInDevelopmentClass = + result.classLoader.loadClass("org.wordpress.android.util.config.FeaturesInDevelopment") + val featuresInDevelopmentObject = featuresInDevelopmentClass.kotlin.objectInstance + assertThat( + featuresInDevelopmentClass.getDeclaredField("featuresInDevelopment") + .apply { isAccessible = true } + .get(featuresInDevelopmentObject) + .cast>() + ).containsOnly("DevFeature") + } + private fun compile(src: List) = KotlinCompilation().apply { sources = src + fakeAppConfig annotationProcessors = listOf(RemoteConfigProcessor()) From 305cf90e412d0e5c886ea5def6d2e9d0610e95bf Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 21 Feb 2024 18:26:47 +0100 Subject: [PATCH 5/5] style: fix checkstyle --- .../wordpress/android/processor/RemoteConfigProcessorTest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt index b6ab50d085fe..b4ee3385c125 100644 --- a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -7,7 +7,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.cast import org.junit.Test class RemoteConfigProcessorTest { - @Test fun `given a class with features annotation, when compiling, generate expected configuration check`() { // when @@ -55,7 +54,7 @@ class RemoteConfigProcessorTest { } @Test - fun `given class with feature and class with experiment annotation, when compiling, generate expected config defaults class`() { + fun `given class with feature and experiment annotation, when compiling, generate config defaults class`() { // given val experiment = SourceFile.kotlin( "Experiment.kt", """ @@ -149,5 +148,4 @@ class RemoteConfigProcessorTest { class FeatureA(appConfig: AppConfig, val remoteField: String ="foo") """ ) - }