diff --git a/instrumentation/CHANGELOG.md b/instrumentation/CHANGELOG.md index fa5f4a84..3f67594c 100644 --- a/instrumentation/CHANGELOG.md +++ b/instrumentation/CHANGELOG.md @@ -4,6 +4,7 @@ Change Log ## Unreleased - Update formatting of instrumentation test names to prevent breaking generation of log files in newer versions of AGP (#263) - Add support for test sharding (#270) +- Add support for inherited tests (#288) ## 1.3.0 (2021-09-17) diff --git a/instrumentation/core/build.gradle.kts b/instrumentation/core/build.gradle.kts index 26d8c5d7..3c32e35f 100644 --- a/instrumentation/core/build.gradle.kts +++ b/instrumentation/core/build.gradle.kts @@ -72,6 +72,7 @@ junitPlatform { tasks.withType { kotlinOptions.jvmTarget = javaVersion.toString() + kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all") } tasks.withType { diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClass.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClass.java new file mode 100644 index 00000000..54a59243 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClass.java @@ -0,0 +1,14 @@ +package de.mannodermaus.junit5.inheritance; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +abstract class JavaAbstractClass { + @Test + void javaTest() { + assertNotNull(getJavaFileName()); + } + + abstract String getJavaFileName(); +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClassTest.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClassTest.java new file mode 100644 index 00000000..11d40f93 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaAbstractClassTest.java @@ -0,0 +1,11 @@ +package de.mannodermaus.junit5.inheritance; + +import androidx.annotation.Nullable; + +public class JavaAbstractClassTest extends JavaAbstractClass { + @Nullable + @Override + public String getJavaFileName() { + return "hello world"; + } +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterface.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterface.java new file mode 100644 index 00000000..e37e4c48 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterface.java @@ -0,0 +1,12 @@ +package de.mannodermaus.junit5.inheritance; + +import org.junit.jupiter.api.Test; + +interface JavaInterface { + @Test + default void javaTest() { + assert(getJavaValue() > 0L); + } + + long getJavaValue(); +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterfaceTest.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterfaceTest.java new file mode 100644 index 00000000..3888d0b4 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaInterfaceTest.java @@ -0,0 +1,8 @@ +package de.mannodermaus.junit5.inheritance; + +public class JavaInterfaceTest implements JavaInterface { + @Override + public long getJavaValue() { + return 4815162342L; + } +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaMixedInterfaceTest.java b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaMixedInterfaceTest.java new file mode 100644 index 00000000..20402c2f --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/JavaMixedInterfaceTest.java @@ -0,0 +1,13 @@ +package de.mannodermaus.junit5.inheritance; + +public class JavaMixedInterfaceTest implements JavaInterface, KotlinInterface { + @Override + public long getJavaValue() { + return 4815162342L; + } + + @Override + public int getKotlinValue() { + return 10101010; + } +} diff --git a/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/KotlinInheritanceTests.kt b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/KotlinInheritanceTests.kt new file mode 100644 index 00000000..a0fd3ea7 --- /dev/null +++ b/instrumentation/core/src/androidTest/java/de/mannodermaus/junit5/inheritance/KotlinInheritanceTests.kt @@ -0,0 +1,35 @@ +package de.mannodermaus.junit5.inheritance + +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test + +abstract class KotlinAbstractClass { + @Test + fun kotlinTest() { + assertNotNull(getKotlinFileName()) + } + + abstract fun getKotlinFileName(): String? +} + +interface KotlinInterface { + @Test + fun kotlinTest() { + assert(kotlinValue > 0) + } + + val kotlinValue: Int +} + +class KotlinAbstractClassTest : KotlinAbstractClass() { + override fun getKotlinFileName() = "hello world" +} + +class KotlinInterfaceTest : KotlinInterface { + override val kotlinValue: Int = 1337 +} + +class KotlinMixedInterfaceTest : KotlinInterface, JavaInterface { + override val kotlinValue: Int = 1337 + override fun getJavaValue(): Long = 1234L +} diff --git a/instrumentation/gradle/wrapper/gradle-wrapper.properties b/instrumentation/gradle/wrapper/gradle-wrapper.properties index 8049c684..ae04661e 100644 --- a/instrumentation/gradle/wrapper/gradle-wrapper.properties +++ b/instrumentation/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/extensions/ClassExt.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/extensions/ClassExt.kt index 36394dc3..16474760 100644 --- a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/extensions/ClassExt.kt +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/extensions/ClassExt.kt @@ -9,29 +9,35 @@ private val jupiterTestAnnotations = listOf( "org.junit.jupiter.api.TestFactory", "org.junit.jupiter.api.RepeatedTest", "org.junit.jupiter.api.TestTemplate", - "org.junit.jupiter.params.ParameterizedTest" + "org.junit.jupiter.params.ParameterizedTest", ) -internal fun Class<*>.jupiterTestMethods(): List { - val allJupiterMethods = mutableListOf() +internal fun Class<*>.jupiterTestMethods(): Set = + jupiterTestMethods(includeInherited = true) + +private fun Class<*>.jupiterTestMethods(includeInherited: Boolean): Set = buildSet { try { // Check each method in the Class for the presence - // of the well-known list of JUnit Jupiter annotations - allJupiterMethods += declaredMethods.filter { method -> - val annotationClassNames = - method.declaredAnnotations.map { it.annotationClass.qualifiedName } - jupiterTestAnnotations.firstOrNull { annotation -> - annotationClassNames.contains(annotation) - } != null - } + // of the well-known list of JUnit Jupiter annotations. + addAll(declaredMethods.filterAnnotatedByJUnitJupiter()) // Recursively check inner classes as well declaredClasses.forEach { inner -> - allJupiterMethods += inner.jupiterTestMethods() + addAll(inner.jupiterTestMethods(includeInherited = false)) + } + + // Attach methods from inherited superclass or (for Java) implemented interfaces, too + if (includeInherited) { + addAll(superclass?.jupiterTestMethods(includeInherited = true).orEmpty()) + interfaces.forEach { i -> addAll(i.jupiterTestMethods(includeInherited = true)) } } } catch (t: Throwable) { Log.w(LOG_TAG, "${t.javaClass.name} in 'hasJupiterTestMethods()' for $name", t) } - - return allJupiterMethods } + +private fun Array.filterAnnotatedByJUnitJupiter(): List = + filter { method -> + val names = method.declaredAnnotations.map { it.annotationClass.qualifiedName } + jupiterTestAnnotations.any { it in names } + } diff --git a/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/TestClasses.kt b/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/TestClasses.kt index 3fe7e664..e7a6f067 100644 --- a/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/TestClasses.kt +++ b/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/TestClasses.kt @@ -80,4 +80,24 @@ class HasTaggedTest { fun method() { } -} \ No newline at end of file +} + +abstract class AbstractTestClass { + @Test + fun abstractTest() { + } +} + +interface AbstractTestInterface { + @Test + fun interfaceTest() { + } +} + +class HasInheritedTestsFromClass : AbstractTestClass() { + @Test + fun method() { + } +} + +class HasInheritedTestsFromInterface : AbstractTestInterface diff --git a/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/internal/ExtensionsTests.kt b/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/internal/ExtensionsTests.kt index 1a1b55d5..e7e86c6d 100644 --- a/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/internal/ExtensionsTests.kt +++ b/instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/internal/ExtensionsTests.kt @@ -5,6 +5,8 @@ package de.mannodermaus.junit5.internal import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import de.mannodermaus.junit5.DoesntHaveTestMethods +import de.mannodermaus.junit5.HasInheritedTestsFromClass +import de.mannodermaus.junit5.HasInheritedTestsFromInterface import de.mannodermaus.junit5.HasInnerClassWithTest import de.mannodermaus.junit5.HasParameterizedTest import de.mannodermaus.junit5.HasRepeatedTest @@ -49,7 +51,7 @@ class ExtensionsTests { ) AndroidJUnit5(klass, params).run(notifier) - assertWithMessage("Executed ${listener.count()} instead of $expectExecutedTests tests: '${listener.methodNames()}'") + assertWithMessage("Executed ${listener.count()} instead of $expectExecutedTests tests on class '${klass.simpleName}': '${listener.methodNames()}'") .that(listener.count()) .isEqualTo(expectExecutedTests) } @@ -87,7 +89,9 @@ class ExtensionsTests { Arguments.of(HasTestFactory::class.java, 2), Arguments.of(HasTestTemplate::class.java, 2), Arguments.of(HasParameterizedTest::class.java, 2), - Arguments.of(HasInnerClassWithTest::class.java, 1) + Arguments.of(HasInnerClassWithTest::class.java, 1), + Arguments.of(HasInheritedTestsFromClass::class.java, 2), + Arguments.of(HasInheritedTestsFromInterface::class.java, 1), ) } }