From 15838aff39f71d26d10fe20b6c212a06924c8f5f Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 20 Nov 2019 19:11:11 +0100 Subject: [PATCH 1/3] Delete deprecated "api" artifact From now on, it's all on "android-test-core" and "android-test-runner", baby --- buildSrc/src/main/kotlin/Artifacts.kt | 10 - buildSrc/src/main/kotlin/Environment.kt | 1 - instrumentation/api/build.gradle.kts | 114 ----- .../api/src/androidTest/AndroidManifest.xml | 13 - .../test/ActivityTestIntegrationTests.kt | 117 ----- .../de/mannodermaus/junit5/test/Extensions.kt | 11 - .../test/JavaInteropVerificationTests.java | 67 --- .../junit5/test/activities/FirstActivity.kt | 25 - .../junit5/test/activities/OtherActivity.kt | 13 - .../androidTest/res/layout/activity_first.xml | 14 - .../layout/activity_first_otherpackage.xml | 14 - .../androidTest/res/layout/activity_other.xml | 14 - .../api/src/main/AndroidManifest.xml | 1 - .../de/mannodermaus/junit5/ActivityTest.kt | 436 ------------------ .../de/mannodermaus/junit5/Exceptions.kt | 23 - .../de/mannodermaus/junit5/Reflection.kt | 42 -- .../de/mannodermaus/junit5/test/ExpectThat.kt | 85 ---- .../junit5/test/TestedInternalTests.kt | 152 ------ instrumentation/settings.gradle.kts | 1 - 19 files changed, 1153 deletions(-) delete mode 100644 instrumentation/api/build.gradle.kts delete mode 100644 instrumentation/api/src/androidTest/AndroidManifest.xml delete mode 100644 instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/ActivityTestIntegrationTests.kt delete mode 100644 instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/Extensions.kt delete mode 100644 instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/JavaInteropVerificationTests.java delete mode 100644 instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/FirstActivity.kt delete mode 100644 instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/OtherActivity.kt delete mode 100644 instrumentation/api/src/androidTest/res/layout/activity_first.xml delete mode 100644 instrumentation/api/src/androidTest/res/layout/activity_first_otherpackage.xml delete mode 100644 instrumentation/api/src/androidTest/res/layout/activity_other.xml delete mode 100644 instrumentation/api/src/main/AndroidManifest.xml delete mode 100644 instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt delete mode 100644 instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Exceptions.kt delete mode 100644 instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Reflection.kt delete mode 100644 instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/ExpectThat.kt delete mode 100644 instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/TestedInternalTests.kt diff --git a/buildSrc/src/main/kotlin/Artifacts.kt b/buildSrc/src/main/kotlin/Artifacts.kt index f193e636..6f0bb78d 100644 --- a/buildSrc/src/main/kotlin/Artifacts.kt +++ b/buildSrc/src/main/kotlin/Artifacts.kt @@ -49,16 +49,6 @@ object Artifacts { private val currentVersion = "1.2.0-SNAPSHOT" val latestStableVersion = "1.1.0" - val Library = Deployed( - platform = Android(minSdk = 26), - groupId = groupId, - artifactId = "android-instrumentation-test", - currentVersion = "0.3.0-SNAPSHOT", - latestStableVersion = "0.2.2", - license = license, - description = "(DEPRECATED) Extensions for instrumented Android tests with JUnit 5." - ) - val Core = Deployed( platform = Android(minSdk = 14), groupId = groupId, diff --git a/buildSrc/src/main/kotlin/Environment.kt b/buildSrc/src/main/kotlin/Environment.kt index fa18abf5..ce88d673 100644 --- a/buildSrc/src/main/kotlin/Environment.kt +++ b/buildSrc/src/main/kotlin/Environment.kt @@ -7,5 +7,4 @@ object Android { const val sampleMinSdkVersion = 14 val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Platform.Android).minSdk - val instrumentationMinSdkVersion = (Artifacts.Instrumentation.Library.platform as Platform.Android).minSdk } diff --git a/instrumentation/api/build.gradle.kts b/instrumentation/api/build.gradle.kts deleted file mode 100644 index 8c1c2fa2..00000000 --- a/instrumentation/api/build.gradle.kts +++ /dev/null @@ -1,114 +0,0 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("com.android.library") - kotlin("android") -} - -android { - compileSdkVersion(Android.compileSdkVersion) - - dexOptions { - javaMaxHeapSize = Android.javaMaxHeapSize - } - - defaultConfig { - minSdkVersion(Android.instrumentationMinSdkVersion) - targetSdkVersion(Android.targetSdkVersion) - versionCode = 1 - versionName = "1.0" - multiDexEnabled = true - - // Usually, this is automatically applied through the Gradle Plugin - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - testInstrumentationRunnerArgument("runnerBuilder", - "de.mannodermaus.junit5.AndroidJUnit5Builder") - } - - sourceSets { - getByName("main").java.srcDir("src/main/kotlin") - getByName("test").java.srcDir("src/test/kotlin") - getByName("androidTest").java.srcDir("src/androidTest/kotlin") - } - - compileOptions { - setSourceCompatibility(JavaVersion.VERSION_1_8) - setTargetCompatibility(JavaVersion.VERSION_1_8) - } - - lintOptions { - // JUnit 4 refers to java.lang.management APIs, which are absent on Android. - warning("InvalidPackage") - } - - packagingOptions { - exclude("META-INF/LICENSE.md") - exclude("META-INF/LICENSE-notice.md") - } - - testOptions { - unitTests.apply { - isReturnDefaultValues = true - } - } -} - -tasks.withType { - kotlinOptions.jvmTarget = "1.8" -} - -tasks.withType { - failFast = true - testLogging { - events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - exceptionFormat = TestExceptionFormat.FULL - } -} - -val commonTestImplementation = configurations.create("commonTestImplementation") -configurations { - getByName("androidTestImplementation").extendsFrom(commonTestImplementation) - getByName("testImplementation").extendsFrom(commonTestImplementation) -} - -dependencies { - implementation(Libs.kotlin_stdlib) - implementation(Libs.androidx_test_runner) - implementation(Libs.junit_jupiter_api) - - // This is required by the "instrumentation-runner" companion library, - // since it can't provide any JUnit 5 runtime libraries itself - // due to fear of prematurely incrementing the minSdkVersion requirement. - runtimeOnly(Libs.junit_platform_runner) - - commonTestImplementation(Libs.truth) - commonTestImplementation(Libs.assertj_core) - commonTestImplementation(Libs.mockito_core) - commonTestImplementation(Libs.junit_jupiter_api) - commonTestImplementation(Libs.junit_jupiter_engine) - - androidTestImplementation(Libs.truth_android) - androidTestImplementation(Libs.espresso_core) - - androidTestRuntimeOnly(project(":runner")) - - // Obviously, these dependencies should be mostly "runtimeOnly", - // but we have to override bundled APIs from the IDE as much as possible for Android Studio. - testImplementation(Libs.junit_platform_engine) - testImplementation(Libs.junit_platform_launcher) - testImplementation(Libs.junit_jupiter_api) - testImplementation(Libs.junit_jupiter_engine) - testImplementation(Libs.junit_vintage_engine) -} - -// ------------------------------------------------------------------------------------------------ -// Deployment Setup -// -// Releases are pushed to jcenter via Bintray, while snapshots are pushed to Sonatype OSS. -// This section defines the necessary tasks to push new releases and snapshots using Gradle tasks. -// ------------------------------------------------------------------------------------------------ - -val deployConfig by extra { Artifacts.Instrumentation.Library } -apply(from = "$rootDir/gradle/deployment.gradle") diff --git a/instrumentation/api/src/androidTest/AndroidManifest.xml b/instrumentation/api/src/androidTest/AndroidManifest.xml deleted file mode 100644 index 2baf4f45..00000000 --- a/instrumentation/api/src/androidTest/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/ActivityTestIntegrationTests.kt b/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/ActivityTestIntegrationTests.kt deleted file mode 100644 index 463ffd83..00000000 --- a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/ActivityTestIntegrationTests.kt +++ /dev/null @@ -1,117 +0,0 @@ -package de.mannodermaus.junit5.test - -import android.content.Intent -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.ext.truth.content.IntentSubject.assertThat -import de.mannodermaus.junit5.ActivityAlreadyLaunchedException -import de.mannodermaus.junit5.ActivityNotLaunchedException -import de.mannodermaus.junit5.ActivityTest -import de.mannodermaus.junit5.Tested -import de.mannodermaus.junit5.test.activities.FirstActivity -import de.mannodermaus.junit5.test.activities.OtherActivity -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -@ActivityTest(FirstActivity::class) -class ActivityTestIntegrationTests { - - @Test - @DisplayName("Launch Automatically: No Parameters") - fun launchAutomaticallyNoParameters() { - onView(withId(R.id.textView)).check(matches(withText("Hello World!"))) - } - - @Test - @DisplayName("Launch Automatically: Activity Parameter") - fun launchAutomaticallyActivityParameter(activity: FirstActivity) { - assertEquals(activity.textView.text, "Hello World!") - } - - @Test - @DisplayName("Launch Automatically: Tested Parameter") - fun launchAutomaticallyTestedParameter(tested: Tested) { - assertEquals(tested.activity!!.textView.text, "Hello World!") - } - - @Test - @ActivityTest(OtherActivity::class) - @DisplayName("Method Parameter overrides class-level declaration 1") - fun methodLevelParameterOverridesClassLevelDeclaration1(activity: OtherActivity) { - } - - @Test - @ActivityTest(FirstActivity::class, launchActivity = false) - @DisplayName("Launching twice causes Exception") - fun launchingTwiceCausesException(tested: Tested) { - tested.launchActivity() - assertThrows(ActivityAlreadyLaunchedException::class.java) { - tested.launchActivity() - } - } - - @Test - @DisplayName("Finishing twice causes Exception") - fun finishingTwiceCausesException(tested: Tested) { - tested.finishActivity() - assertThrows(ActivityNotLaunchedException::class.java) { - tested.finishActivity() - } - } - - @Test - @ActivityTest( - FirstActivity::class, - launchFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY) - @DisplayName("Launch Flags are properly applied") - fun launchFlagsAreProperlyApplied(activity: FirstActivity) { - assertIntentHasFlag(activity.intent, Intent.FLAG_ACTIVITY_NO_HISTORY) - } - - @Test - @ActivityTest( - FirstActivity::class, - targetPackage = "some.weird.other.package", - launchActivity = false) - @DisplayName("Target Package is properly applied") - fun targetPackageIsProperlyProperlyApplied(tested: Tested) { - val error = assertThrows(RuntimeException::class.java) { - tested.launchActivity() - } - - assertThat(error.message) - .contains("Could not launch activity") - - assertThat(error.cause?.message) - .contains("cmp=some.weird.other.package/") - .contains("FirstActivity") - } - - @Test - @ActivityTest( - FirstActivity::class, - launchActivity = false) - @DisplayName("Launching with custom Intent") - fun launchingWithCustomIntent(tested: Tested) { - val intent = Intent().apply { - action = "custom.intent.action" - putExtra("extraArgument", "YOLO") - putExtra("intArgument", 1337) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) - } - - val activity = tested.launchActivity(intent) - assertThat(activity.intent).apply { - hasAction("custom.intent.action") - extras().string("extraArgument").isEqualTo("YOLO") - extras().integer("intArgument").isEqualTo(1337) - } - - assertIntentHasFlag(activity.intent, Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) - } -} diff --git a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/Extensions.kt b/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/Extensions.kt deleted file mode 100644 index 4270fc90..00000000 --- a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/Extensions.kt +++ /dev/null @@ -1,11 +0,0 @@ -package de.mannodermaus.junit5.test - -import android.content.Intent -import org.junit.jupiter.api.Assertions.assertEquals - -fun assertIntentHasFlag(intent: Intent, flag: Int) { - assertEquals( - flag, - intent.flags and flag, - "Expected Intent flag wasn't provided to Activity. Expected $flag, but was ${intent.flags}") -} diff --git a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/JavaInteropVerificationTests.java b/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/JavaInteropVerificationTests.java deleted file mode 100644 index 2714ea28..00000000 --- a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/JavaInteropVerificationTests.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.mannodermaus.junit5.test; - -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Intent; -import de.mannodermaus.junit5.ActivityTest; -import de.mannodermaus.junit5.Tested; -import de.mannodermaus.junit5.test.activities.FirstActivity; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.ext.truth.content.IntentSubject.assertThat; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Verification that the library APIs - * still kinda look good when invoked from Java. - * - * This class serves purely as syntactical validation, - * and doesn't actually conduct meaningful tests itself - - * check the {@link ActivityTestIntegrationTests} for that sort of thing. - */ -@ActivityTest(FirstActivity.class) -class JavaInteropVerificationTests { - - @Test - @DisplayName("Launch without Parameters") - void launchAutomaticallyNoParameters() { - } - - @Test - @DisplayName("Launch with Activity Parameter") - void launchAutomaticallyActivityParameter(FirstActivity activity) { - } - - @Test - @ActivityTest(value = FirstActivity.class, launchActivity = false) - @DisplayName("Tested API Round Trip") - void testedApiRoundTrip(Tested tested) { - FirstActivity launched = tested.launchActivity(null); - - onView(withId(R.id.textView)).perform(click()); - - Instrumentation.ActivityResult result = tested.getActivityResult(); - assertThat(result.getResultCode()).isEqualTo(Activity.RESULT_OK); - assertThat(result.getResultData()).extras().integer("returnValue").isEqualTo(1337); - - FirstActivity retrieved = tested.getActivity(); - assertThat(launched).isEqualTo(retrieved); - - tested.finishActivity(); - FirstActivity absent = tested.getActivity(); - assertThat(absent).isNull(); - - FirstActivity launchedAgain = tested.launchActivity(null); - assertThat(launchedAgain).isNotEqualTo(launched); - } - - @Test - @ActivityTest(value = FirstActivity.class, targetPackage = "some.other.weird.package", launchFlags = Intent.FLAG_ACTIVITY_NO_HISTORY, initialTouchMode = false, launchActivity = false) - @DisplayName("Configuration parameters in Annotation") - void bloatedConfigAnnotation(Tested tested) { - } -} diff --git a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/FirstActivity.kt b/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/FirstActivity.kt deleted file mode 100644 index 0ac27a73..00000000 --- a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/FirstActivity.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.mannodermaus.junit5.test.activities - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.widget.TextView -import de.mannodermaus.junit5.test.R - -class FirstActivity : Activity() { - - lateinit var textView: TextView - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_first) - - this.textView = findViewById(R.id.textView) - this.textView.setOnClickListener { - setResult(RESULT_OK, Intent().apply { - putExtra("returnValue", 1337) - }) - finish() - } - } -} diff --git a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/OtherActivity.kt b/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/OtherActivity.kt deleted file mode 100644 index d8999afb..00000000 --- a/instrumentation/api/src/androidTest/kotlin/de/mannodermaus/junit5/test/activities/OtherActivity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.mannodermaus.junit5.test.activities - -import android.app.Activity -import android.os.Bundle -import de.mannodermaus.junit5.test.R - -class OtherActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_other) - } -} diff --git a/instrumentation/api/src/androidTest/res/layout/activity_first.xml b/instrumentation/api/src/androidTest/res/layout/activity_first.xml deleted file mode 100644 index b1a23aa2..00000000 --- a/instrumentation/api/src/androidTest/res/layout/activity_first.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/instrumentation/api/src/androidTest/res/layout/activity_first_otherpackage.xml b/instrumentation/api/src/androidTest/res/layout/activity_first_otherpackage.xml deleted file mode 100644 index e33f82ca..00000000 --- a/instrumentation/api/src/androidTest/res/layout/activity_first_otherpackage.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/instrumentation/api/src/androidTest/res/layout/activity_other.xml b/instrumentation/api/src/androidTest/res/layout/activity_other.xml deleted file mode 100644 index e1b93938..00000000 --- a/instrumentation/api/src/androidTest/res/layout/activity_other.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/instrumentation/api/src/main/AndroidManifest.xml b/instrumentation/api/src/main/AndroidManifest.xml deleted file mode 100644 index 2b80fac9..00000000 --- a/instrumentation/api/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt b/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt deleted file mode 100644 index f8f1e4e8..00000000 --- a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/ActivityTest.kt +++ /dev/null @@ -1,436 +0,0 @@ -package de.mannodermaus.junit5 - -import android.app.Activity -import android.app.Instrumentation -import android.app.Instrumentation.ActivityResult -import android.content.Intent -import android.os.Bundle -import android.util.Log -import androidx.annotation.VisibleForTesting -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.runner.MonitoringInstrumentation -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.* -import org.junit.platform.commons.support.AnnotationSupport -import java.lang.reflect.Parameter -import java.lang.reflect.ParameterizedType -import kotlin.annotation.AnnotationRetention.RUNTIME -import kotlin.annotation.AnnotationTarget.CLASS -import kotlin.annotation.AnnotationTarget.FUNCTION -import kotlin.reflect.KClass - -/* Constants */ - -private const val DEPRECATION_MESSAGE = """ - Please consider moving to "de.mannodermaus.junit5:test-core" instead, - which provides a new API to connect JUnit 5 tests to Android, - based on AndroidX's ActivityScenario. - More info can be found on the android-junit5 repository at https://github.com/mannodermaus/android-junit5. -""" - -private const val ABSENT_TARGET_PACKAGE = "-" -private const val NO_FLAGS_SET = 0 -private const val DEFAULT_LAUNCH_ACTIVITY = true -private const val DEFAULT_INITIAL_TOUCH_MODE = true - -private const val LOG_TAG = "ActivityTest" - -/* - * ================================================================================================ - * Public API - * ================================================================================================ - */ - -/** - * Marker annotation providing functional testing of an [Activity], applied - * to either a single method annotated with [Test], or the class in which - * it is contained. What used to be ActivityTestRule is now this annotation. - * - * If [launchActivity] is set to true, the Activity under test - * will be launched automatically before the test is executing. This is also - * the default behaviour if the parameter is unspecified. If this is undesired, - * the Activity can be launched manually by adding a parameter of type [Tested] - * to the method in question. - * - * @param value The activity under test. This must be a class in the instrumentation - * targetPackage specified in the AndroidManifest.xml - * @param targetPackage The name of the target package that the Activity is started under - * @param launchFlags [Intent] flags to start the Activity under test with - * @param launchActivity Whether or not to automatically launch the Activity before the test execution - */ -@Deprecated(message = DEPRECATION_MESSAGE) -@Retention(RUNTIME) -@Target(CLASS, FUNCTION) -@ExtendWith(ActivityTestExtension::class) -annotation class ActivityTest( - val value: KClass, - val targetPackage: String = ABSENT_TARGET_PACKAGE, - val launchFlags: Int = Intent.FLAG_ACTIVITY_NEW_TASK, - val initialTouchMode: Boolean = DEFAULT_INITIAL_TOUCH_MODE, - val launchActivity: Boolean = DEFAULT_LAUNCH_ACTIVITY) - -/** - * Controller object representing an Activity under test. - * It wraps around the current Activity and provides functionality - * related to launching, finishing and recreating the Activity under test. - * - * To obtain an instance, add a parameter of type [Tested] to your test method - * and assign it the generic type of the Activity described in the scope's [ActivityTest]. - */ -@Deprecated(message = DEPRECATION_MESSAGE) -interface Tested { - - /** - * Obtains the current Activity under test, if any. - */ - val activity: T? - - /** - * Launches the Activity under test. - * - * Don't call this method directly, unless you explicitly requested - * to launch the Activity manually through the [ActivityTest]'s launchActivity flag. - * - * @throws ActivityAlreadyLaunchedException if the Activity was already launched - */ - fun launchActivity(intent: Intent? = null): T - - /** - * Finishes the currently launched Activity. - * - * @throws ActivityNotLaunchedException if the Activity is not running - */ - fun finishActivity() - - /** - * This method can be used to retrieve the result of an Activity that has called [Activity.setResult]. - * Usually, the result is handled in onActivityResult of the parent activity, which has called startActivityForResult. - * This method must not be called before Activity.finish was called. - * - * @throws ActivityNotLaunchedException if the Activity is not running - */ - fun getActivityResult(): ActivityResult -} - -/* - * ================================================================================================ - * Internal API - * ================================================================================================ - */ - -/** - * Internal members of the [Tested] interface, exposed only to the [ActivityTestExtension], - * not to consumers of the library. It allows access to validate the parameters assigned - * to a certain test environment, as well as some replacement of production resources - * with mocks during unit testing. - * - * Instances of actual implementing extensions to this abstract class are obtained - * via the accompanying [TestedInternal.Factory] class. - */ -@VisibleForTesting -internal abstract class TestedInternal( - protected val parameterTypes: List -) : Tested { - - abstract fun parameterTypeAt(index: Int): ParameterType - abstract fun validateParameterAt(index: Int): Boolean - abstract fun setInstrumentation(instrumentation: Instrumentation) - - fun validateParameters() = (0 until parameterTypes.size).all { validateParameterAt(it) } - - open fun onBeforeTestExecution() {} - open fun onAfterTestExecution() {} - - /** - * Creates concrete implementations of [TestedInternal] objects, - * given a set of configuration parameters. - */ - class Factory { - fun create( - activityClass: Class, - parameterTypes: List, - targetPackage: String, - launchFlags: Int, - initialTouchMode: Boolean, - launchActivity: Boolean): TestedInternal { - return TestedImpl( - activityClass = activityClass, - targetPackage = targetPackage, - launchFlags = launchFlags, - initialTouchMode = initialTouchMode, - launchActivity = launchActivity, - parameterTypes = parameterTypes) - } - } -} - -/** - * Concrete realization of the [TestedInternal] contract - * used by the [ActivityTestExtension]. - * - * It takes the place of JUnit 4's "ActivityTestRule", - * delegating to a surrounding [Instrumentation] and - * keeping track of the state of a launched [Activity]. - */ -@VisibleForTesting -internal class TestedImpl( - private val activityClass: Class, - private val targetPackage: String = ABSENT_TARGET_PACKAGE, - private val launchFlags: Int = NO_FLAGS_SET, - private val initialTouchMode: Boolean = DEFAULT_INITIAL_TOUCH_MODE, - private val launchActivity: Boolean = DEFAULT_LAUNCH_ACTIVITY, - parameterTypes: List) - : TestedInternal(parameterTypes) { - - // Used to override the default Instrumentation, obtained from the registry - // (primary application: Unit Testing) - private var _instrumentation: Instrumentation? = null - private val instrumentation - get() = _instrumentation ?: InstrumentationRegistry.getInstrumentation() - - private var _activity: T? = null - override val activity get() = _activity - - @Suppress("UNCHECKED_CAST") - override fun launchActivity(intent: Intent?): T { - if (activity != null) { - throw ActivityAlreadyLaunchedException() - } - - instrumentation.setInTouchMode(this.initialTouchMode) - - // Construct launcher Intent, injecting configuration along the way - val startIntent = intent ?: Intent(Intent.ACTION_MAIN) - - if (startIntent.component == null) { - // Fall back to the default Target Context's package name if none is set - val targetPackage = if (this.targetPackage == ABSENT_TARGET_PACKAGE) { - InstrumentationRegistry.getInstrumentation().targetContext.packageName - } else { - this.targetPackage - } - startIntent.setClassName(targetPackage, activityClass.name) - } - - if (startIntent.flags == NO_FLAGS_SET) { - startIntent.addFlags(this.launchFlags) - } - - Log.i(LOG_TAG, "Launching activity: ${startIntent.component} with Intent: $startIntent") - - this._activity = this.activityClass.cast(instrumentation.startActivitySync(startIntent)) as T - - instrumentation.waitForIdleSync() - - if (activity == null) { - // Log an error message because the Activity failed to launch - val errorMessage = "$LOG_TAG Activity ${startIntent.component} failed to launch" - val bundle = Bundle() - bundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, errorMessage) - instrumentation.sendStatus(0, bundle) - Log.e(LOG_TAG, errorMessage) - } - - // Blow up if necessary - return activity!! - } - - override fun finishActivity() { - val activity = this.activity ?: throw ActivityNotLaunchedException() - - activity.finish() - this._activity = null - instrumentation.waitForIdleSync() - } - - override fun getActivityResult(): Instrumentation.ActivityResult { - val activity = this.activity ?: throw ActivityNotLaunchedException() - return activity.result - } - - override fun setInstrumentation(instrumentation: Instrumentation) { - this._instrumentation = instrumentation - } - - override fun parameterTypeAt(index: Int): ParameterType = parameterTypes[index] - - override fun onBeforeTestExecution() { - super.onBeforeTestExecution() - - // Check for undesirable states: - // * Lacking a Tested parameter with manual Activity launching is useless - if (!this.launchActivity && !parameterTypes.contains(ParameterType.ValidTestedWrapper)) { - throw MissingTestedParameterException(this.activityClass) - } - - // From here on, this method is mirroring the first half of - // ActivityTestRule.ActivityStatement#evaluate(). - // TODO Include ActivityFactory checks eventually -// val monitoringInstrumentation = this.instrumentation as? MonitoringInstrumentation - - if (this.launchActivity) { - launchActivity(null) - } - } - - override fun onAfterTestExecution() { - super.onAfterTestExecution() - - // This method is mirroring the second half of - // ActivityTestRule.ActivityStatement#evaluate(). - val monitoringInstrumentation = this.instrumentation as? MonitoringInstrumentation - monitoringInstrumentation?.useDefaultInterceptingActivityFactory() - - if (activity != null) { - finishActivity() - } - } - - override fun validateParameterAt(index: Int): Boolean { - val type = parameterTypes[index] - - return when (type) { - // Possibly a developer error; throw a descriptive exception - is ParameterType.InvalidTestedWrapper -> throw UnexpectedActivityException( - expected = this.activityClass, - actual = type.actual) - - // Otherwise, communicate only valid parameter types - else -> type.valid - } - } -} - -/** - * JUnit Platform Extension revolving around support - * for Activity-based instrumentation testing on Android. - * - * This Extension takes the place of the ActivityTestRule - * from the JUnit4-centered Test Support Library. - */ -@VisibleForTesting -internal class ActivityTestExtension : BeforeTestExecutionCallback, ParameterResolver, AfterTestExecutionCallback { - - private lateinit var delegate: TestedInternal - - @VisibleForTesting - val delegateFactory = TestedInternal.Factory() - - /* BeforeTestExecution */ - - override fun beforeTestExecution(context: ExtensionContext) { - // Construct a controlling Delegate to drive the test with - val config = context.findActivityTestConfig() ?: return - val parameterTypes = context.requiredTestMethod.parameters - .map { it.describeTypeInRelationToClass(config.value) } - - this.delegate = delegateFactory.create( - activityClass = config.value.java, - targetPackage = config.targetPackage, - launchFlags = config.launchFlags, - initialTouchMode = config.initialTouchMode, - launchActivity = config.launchActivity, - parameterTypes = parameterTypes) - this.delegate.onBeforeTestExecution() - } - - private fun ExtensionContext.findActivityTestConfig(): ActivityTest? = - sequenceOf(element, parent.flatMap { it.element }) - .filter { it.isPresent } - .map { AnnotationSupport.findAnnotation(it.get(), ActivityTest::class.java) } - .filter { it.isPresent } - .map { it.get() } - .firstOrNull() - - private fun Parameter.describeTypeInRelationToClass(targetClass: KClass<*>): ParameterType { - val type = parameterizedType - val activityClassJava = targetClass.java - - when (type) { - is Class<*> -> { - if (type == activityClassJava) { - return ParameterType.Activity - } - } - is ParameterizedType -> { - if (type.rawType == Tested::class.java) { - val argumentType = type.actualTypeArguments[0] as Class<*> - return if (argumentType == activityClassJava) { - ParameterType.ValidTestedWrapper - - } else { - ParameterType.InvalidTestedWrapper(argumentType) - } - } - } - } - - return ParameterType.Unknown - } - - /* ParameterResolver */ - - override fun supportsParameter(parameterContext: ParameterContext, - extensionContext: ExtensionContext): Boolean { - return delegate.validateParameterAt(parameterContext.index) - } - - override fun resolveParameter(parameterContext: ParameterContext, - extensionContext: ExtensionContext): Any? { - val parameterType = delegate.parameterTypeAt(parameterContext.index) - - return when (parameterType) { - // val parameter: Activity - ParameterType.Activity -> delegate.activity - - // val parameter: Tested - ParameterType.ValidTestedWrapper -> delegate - - // Otherwise, library error (supportsParameter() should filter it) - else -> throw IllegalArgumentException( - "Unexpected ParameterType resolution requested for '$parameterType'") - } - } - - /* AfterTestExecution */ - - override fun afterTestExecution(context: ExtensionContext) { - this.delegate.onAfterTestExecution() - } -} - -/** - * Marker values representing the kind of parameter - * used by an [ActivityTest] method. - */ -@VisibleForTesting -internal sealed class ParameterType(val valid: Boolean) { - - /* Positive */ - - /** - * The parameter is equal to the [ActivityTestExtension]'s - * "Activity under test". - */ - object Activity : ParameterType(valid = true) - - /** - * The parameter is a [Tested] controller with the correct - * "Activity under test" sub-type. - */ - object ValidTestedWrapper : ParameterType(valid = true) - - /* Negative */ - - /** - * The parameter is a [Tested] controller, but the sub-type - * doesn't match the declared "Activity under test" in the - * [ActivityTestExtension]. - */ - class InvalidTestedWrapper(val actual: Class<*>) : ParameterType(valid = false) - - /** - * The parameter is of unknown type to the [ActivityTestExtension]. - */ - object Unknown : ParameterType(valid = false) -} diff --git a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Exceptions.kt b/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Exceptions.kt deleted file mode 100644 index 3e3d6457..00000000 --- a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Exceptions.kt +++ /dev/null @@ -1,23 +0,0 @@ -package de.mannodermaus.junit5 - -import android.app.Activity - -class UnexpectedActivityException(expected: Class, actual: Class<*>) - : IllegalArgumentException( - "@ActivityTest type didn't match the requested parameter. " + - "Expected '${expected.name}', was '${actual.name}'") - -class ActivityAlreadyLaunchedException : IllegalStateException( - "Activity has already been launched! " + - "It must be finished by calling finishActivity() " + - "before launchActivity can be called again.") - -class ActivityNotLaunchedException : IllegalStateException( - "Activity was not launched. If you manually finished it, " + - "you must launch it again before finishing it.") - -class MissingTestedParameterException(activityType: Class) : IllegalStateException( - "launchActivity=false without a method parameter of type " + - "${Tested::class.java.simpleName}<${activityType.simpleName}> " + - "won't ever allow you to lazily launch the Activity. " + - "Add it to the method signature!") diff --git a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Reflection.kt b/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Reflection.kt deleted file mode 100644 index 25dc3469..00000000 --- a/instrumentation/api/src/main/kotlin/de/mannodermaus/junit5/Reflection.kt +++ /dev/null @@ -1,42 +0,0 @@ -package de.mannodermaus.junit5 - -import android.app.Activity -import android.app.Instrumentation.ActivityResult -import android.content.Intent - -private const val FIELD_RESULT_CODE = "mResultCode" -private const val FIELD_RESULT_DATA = "mResultData" - -/** - * Helper property to access the result of a given [Activity]. - * This code is mirroring ActivityTestRule#getActivityResult(), - * and will need to be updated in case it blows up on a new - * API version because of internal changes to the structure of an Activity. - */ -internal val Activity.result: ActivityResult - get() { - if (!this.isFinishing) { - throw IllegalStateException("Activity is not finishing!") - } - - try { - val resultCodeField = Activity::class.java.getDeclaredField(FIELD_RESULT_CODE) - resultCodeField.isAccessible = true - - val resultDataField = Activity::class.java.getDeclaredField(FIELD_RESULT_DATA) - resultDataField.isAccessible = true - - return ActivityResult( - resultCodeField.get(this) as Int, - resultDataField.get(this) as Intent?) - - } catch (e: NoSuchFieldException) { - throw RuntimeException( - "Looks like the Android Activity class has changed its " + - "private fields for mResultCode or mResultData. " + - "Time to update the reflection code.", e) - - } catch (e: IllegalAccessException) { - throw RuntimeException("Field mResultCode or mResultData is not accessible", e) - } - } diff --git a/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/ExpectThat.kt b/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/ExpectThat.kt deleted file mode 100644 index 4a68ef86..00000000 --- a/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/ExpectThat.kt +++ /dev/null @@ -1,85 +0,0 @@ -package de.mannodermaus.junit5.test - -import de.mannodermaus.junit5.test.ExpectedMessage.Containing -import de.mannodermaus.junit5.test.ExpectedMessage.EqualTo -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals - -/* - * Small utility framework for a fluent "Expectations API". - * Allows for test declarations à la - * "Expect that throws with message ". - * - * An Expectation starts with a invoking "expectThat()" and ends with "assert()". - */ - -fun expectThat(call: () -> T): ExpectThat = ExpectThat(call) - -class ExpectThat -internal constructor(private val factory: () -> E) { - - fun willThrow(throwableClass: Class) = ExpectThrowable(factory, throwableClass) - fun willReturn(value: E) = ExpectValue(factory, value) -} - -abstract class CompletedExpect -internal constructor(protected val factory: () -> E) { - abstract fun assert() -} - -class ExpectThrowable -internal constructor( - factory: () -> E, - private val throwableClass: Class) - : CompletedExpect(factory) { - - private var messageConstraints: MutableList = mutableListOf() - - fun withMessage(vararg constraints: ExpectedMessage) = also { - this.messageConstraints.addAll(constraints) - } - - override fun assert() { - val throwable = Assertions.assertThrows(throwableClass) { - factory.invoke() - } - - messageConstraints.forEach { expectedMessage -> - val throwableMessage = throwable.message ?: "" - expectedMessage.assertAgainst(throwableMessage) - } - } -} - -class ExpectValue -internal constructor( - factory: () -> E, - private val value: E) - : CompletedExpect(factory) { - - override fun assert() { - assertEquals(value, factory.invoke()) - } -} - -/* Sealed Classes */ - -fun equalTo(expected: String) = EqualTo(expected) -fun containing(fragment: String) = Containing(fragment) - -sealed class ExpectedMessage { - abstract fun assertAgainst(actual: String) - - class EqualTo(private val expected: String) : ExpectedMessage() { - override fun assertAgainst(actual: String) { - assertEquals(expected, actual) - } - } - - class Containing(private val fragment: String) : ExpectedMessage() { - override fun assertAgainst(actual: String) { - assertThat(actual).containsOnlyOnce(fragment) - } - } -} diff --git a/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/TestedInternalTests.kt b/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/TestedInternalTests.kt deleted file mode 100644 index 61725f38..00000000 --- a/instrumentation/api/src/test/kotlin/de/mannodermaus/junit5/test/TestedInternalTests.kt +++ /dev/null @@ -1,152 +0,0 @@ -package de.mannodermaus.junit5.test - -import android.app.Activity -import android.app.Instrumentation -import de.mannodermaus.junit5.ActivityTestExtension -import de.mannodermaus.junit5.MissingTestedParameterException -import de.mannodermaus.junit5.ParameterType -import de.mannodermaus.junit5.TestedInternal -import de.mannodermaus.junit5.UnexpectedActivityException -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito - -class TestedInternalTests { - - private lateinit var mockInstrumentation: Instrumentation - private lateinit var extension: ActivityTestExtension - - @BeforeEach - fun beforeEach() { - this.mockInstrumentation = Mockito.mock(Instrumentation::class.java) - this.extension = ActivityTestExtension() - } - - @Test - @DisplayName("Execution: Works on Activity Parameter") - fun worksWhenUsingActivityParameterType() { - val tested = create(SomeActivity::class.java, ParameterType.Activity) - tested.execute() - } - - @Test - @DisplayName("Execution: Works on Valid Tested Parameter") - fun worksWhenUsingValidTestedParameterType() { - val tested = create(SomeActivity::class.java, ParameterType.ValidTestedWrapper) - tested.execute() - } - - @Test - @DisplayName("Execution: Throws when missing Tested Parameter in manual launch mode") - fun throwsWhenMissingTestedParameterTypeOnManualLaunch() { - val tested = create( - activityClass = SomeActivity::class.java, - launchActivity = false) - - assertThrows(MissingTestedParameterException::class.java) { - tested.execute() - } - } - - @Test - @DisplayName("Execution: Throws when using Activity Parameter in manual launch mode") - fun throwsWhenUsingActivityParameterTypeOnManualLaunch() { - val tested = create( - activityClass = SomeActivity::class.java, - parameterType = ParameterType.Activity, - launchActivity = false) - - assertThrows(MissingTestedParameterException::class.java) { - tested.execute() - } - } - - @Test - @DisplayName("ParameterType Validation: Activity") - internal fun parameterTypeValidationActivity() { - val tested = create( - activityClass = SomeActivity::class.java, - parameterType = ParameterType.Activity) - - expectThat { tested.validateParameters() } - .willReturn(true) - .assert() - } - - @Test - @DisplayName("ParameterType Validation: Valid Tested") - internal fun parameterTypeValidationValidTestedT() { - val tested = create( - activityClass = SomeActivity::class.java, - parameterType = ParameterType.ValidTestedWrapper) - - expectThat { tested.validateParameters() } - .willReturn(true) - .assert() - } - - @Test - @DisplayName("ParameterType Validation: Invalid Tested") - internal fun parameterTypeValidationInvalidTestedT() { - val tested = create( - activityClass = SomeActivity::class.java, - parameterType = ParameterType.InvalidTestedWrapper(OtherActivity::class.java)) - - expectThat { tested.validateParameters() } - .willThrow(UnexpectedActivityException::class.java) - .withMessage( - containing("Expected 'de.mannodermaus.junit5.test.SomeActivity'"), - containing("was 'de.mannodermaus.junit5.test.OtherActivity'")) - .assert() - } - - @Test - @DisplayName("ParameterType Validation: Unknown") - internal fun parameterTypeValidationUnknown() { - val tested = create( - activityClass = SomeActivity::class.java, - parameterType = ParameterType.Unknown) - - expectThat { tested.validateParameters() } - .willReturn(false) - .assert() - } - - /* Private */ - - /** - * Creates a new DefaultTested object with the appropriate mocks in place. - */ - private fun create( - activityClass: Class, - parameterType: ParameterType? = null, - launchActivity: Boolean = true): TestedInternal { - val types = if (parameterType != null) listOf(parameterType) else emptyList() - - val tested = extension.delegateFactory.create( - activityClass = activityClass, - targetPackage = "de.mannodermaus.junit5", - launchActivity = launchActivity, - parameterTypes = types, - launchFlags = 0, - initialTouchMode = true) - - // Configure the Instrumentation mock - val mockActivity = Mockito.mock(activityClass) - Mockito.`when`(mockInstrumentation.startActivitySync(any())).thenReturn(mockActivity) - tested.setInstrumentation(mockInstrumentation) - return tested - } - - private fun TestedInternal.execute() { - onBeforeTestExecution() - onAfterTestExecution() - } -} - -// "Shell Activity" classes, used purely as markers for validation -private open class SomeActivity : Activity() -private open class OtherActivity : Activity() diff --git a/instrumentation/settings.gradle.kts b/instrumentation/settings.gradle.kts index 26f8b74b..adcedef8 100644 --- a/instrumentation/settings.gradle.kts +++ b/instrumentation/settings.gradle.kts @@ -1,6 +1,5 @@ rootProject.name = "android-junit5-instrumentation" -include(":api") include(":core") include(":runner") include(":sample") From 0dfd01471ba43d701f41e07f90388b275433712e Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 20 Nov 2019 19:13:08 +0100 Subject: [PATCH 2/3] Remove api project from Circle CI config --- .circleci/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f1903c2..b7a6ea4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx10248m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError"' cache_key: &cache_key - key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum "plugin/android-junit5/build.gradle.kts" }}-{{ checksum "instrumentation/api/build.gradle.kts" }}-{{ checksum "instrumentation/runner/build.gradle.kts" }}-{{ checksum "instrumentation/sample/build.gradle.kts" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "buildSrc/src/main/kotlin/Artifacts.kt" }}-{{ checksum "buildSrc/src/main/kotlin/Libs.kt" }}-{{ checksum "buildSrc/src/main/kotlin/Versions.kt" }} + key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum "plugin/android-junit5/build.gradle.kts" }}-{{ checksum "instrumentation/runner/build.gradle.kts" }}-{{ checksum "instrumentation/sample/build.gradle.kts" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "buildSrc/src/main/kotlin/Artifacts.kt" }}-{{ checksum "buildSrc/src/main/kotlin/Libs.kt" }}-{{ checksum "buildSrc/src/main/kotlin/Versions.kt" }} version: 2 jobs: @@ -89,9 +89,6 @@ jobs: root: ~/root paths: - project - - store_artifacts: - path: instrumentation/api/build/reports - destination: instrumentation-api - store_artifacts: path: instrumentation/core/build/reports destination: instrumentation-core From 09cba44951ee4fdff2054d5b0abd151d5d8d2a69 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 20 Nov 2019 19:36:12 +0100 Subject: [PATCH 3/3] Remove api project from Gradle tasks --- .circleci/config.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b7a6ea4b..3c6426d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,13 +51,12 @@ jobs: name: (Instrumentation) Build command: | cd instrumentation - ./gradlew :api:assembleDebug \ - :core:assembleDebug :core:assembleDebugAndroidTest \ + ./gradlew :core:assembleDebug :core:assembleDebugAndroidTest \ :runner:assembleDebug \ :sample:assembleDebug --stacktrace --no-daemon - run: name: (Instrumentation) Test - command: cd instrumentation && ./gradlew :api:check :core:check :runner:check --stacktrace --no-daemon + command: cd instrumentation && ./gradlew :core:check :runner:check --stacktrace --no-daemon - run: name: Store Google Service Account command: echo $GCLOUD_SERVICE_KEY > ${HOME}/gcloud-service-key.json