<lambda>null1 package com.android.onboarding.contracts.testing
2
3 import android.app.Activity
4 import android.content.Context
5 import android.content.Intent
6 import androidx.test.core.app.ApplicationProvider
7 import com.android.onboarding.contracts.IntentSerializer
8 import com.android.onboarding.contracts.NodeId
9 import com.android.onboarding.contracts.OnboardingActivityApiContract
10 import com.google.common.truth.Truth.assertThat
11 import com.google.common.truth.Truth.assertWithMessage
12 import kotlin.reflect.KProperty1
13 import kotlin.reflect.full.memberProperties
14 import kotlin.reflect.jvm.isAccessible
15 import org.apache.commons.lang3.ClassUtils.isPrimitiveOrWrapper
16 import org.robolectric.Robolectric
17 import org.robolectric.Shadows.shadowOf
18
19 const val TEST_NODE_ID: NodeId = -666
20
21 private fun isPrimitiveArray(obj: Any): Boolean =
22 obj is ByteArray ||
23 obj is CharArray ||
24 obj is ShortArray ||
25 obj is IntArray ||
26 obj is LongArray ||
27 obj is DoubleArray ||
28 obj is FloatArray ||
29 obj is BooleanArray
30
31 /**
32 * Compares that [lhs] is equal to [rhs] via reflection by recursively checking that all kotlin
33 * properties are equal. The usage of this function should only be reserved for cases where the
34 * objects being checked cannot guarantee a reliable [Any.equals] implementation.
35 *
36 * Behaviour for Java objects is undefined.
37 */
38 fun recursiveReflectionEquals(lhs: Any?, rhs: Any?): Boolean {
39 if (lhs == null || rhs == null) return lhs == rhs
40 if (isPrimitiveOrWrapper(lhs::class.java) || isPrimitiveOrWrapper(rhs::class.java)) {
41 return lhs == rhs
42 }
43 if (isPrimitiveArray(lhs) && isPrimitiveArray(rhs)) {
44 return arrayOf(lhs).contentDeepEquals(arrayOf(rhs))
45 }
46 if (lhs is Array<*> && rhs is Array<*>) {
47 return lhs.zip(rhs).none { (l, r) -> !recursiveReflectionEquals(l, r) }
48 }
49 if (lhs is Iterable<*> && rhs is Iterable<*>) {
50 return lhs.zip(rhs).none { (l, r) -> !recursiveReflectionEquals(l, r) }
51 }
52 val leftProps = lhs::class.memberProperties.associateBy(KProperty1<*, *>::name)
53 val rightProps = rhs::class.memberProperties.associateBy(KProperty1<*, *>::name)
54 @Suppress("UNCHECKED_CAST")
55 return leftProps.none search@{ (lName, lProp) ->
56 val rProp = rightProps[lName] ?: return@search false
57 val lValue =
58 (lProp as KProperty1<Any, *>).run {
59 val accessible = isAccessible
60 isAccessible = true
61 val value = get(lhs)
62 isAccessible = accessible
63 value
64 }
65 val rValue =
66 (rProp as KProperty1<Any, *>).run {
67 val accessible = isAccessible
68 isAccessible = true
69 val value = get(rhs)
70 isAccessible = accessible
71 value
72 }
73 !recursiveReflectionEquals(lValue, rValue)
74 }
75 }
76
77 /** Assert that a contract's arguments encode correctly. */
assertArgumentEncodesCorrectlynull78 fun <I> assertArgumentEncodesCorrectly(contract: OnboardingActivityApiContract<I, *>, argument: I) {
79 val context = ApplicationProvider.getApplicationContext<Context>()
80 val intent = contract.createIntent(context, argument)
81 val out = contract.extractArgument(intent)
82
83 assertThat(recursiveReflectionEquals(out, argument)).isTrue()
84 }
85
86 /**
87 * Assert that a contract's result encodes correctly.
88 *
89 * <p>Due to an implementation detail, tests using this must be using RobolectricTestRunner
90 */
assertReturnValueEncodesCorrectlynull91 fun <O> assertReturnValueEncodesCorrectly(contract: OnboardingActivityApiContract<*, O>, value: O) {
92 val controller = Robolectric.buildActivity(Activity::class.java)
93 /*
94 * Cannot use [AutoCloseable::use] since [ActivityController]
95 * does not implement [AutoCloseable] on AOSP
96 */
97 try {
98 val activity = controller.get()
99 contract.setResult(activity, value)
100 val shadowActivity = shadowOf(activity)
101 val result = contract.parseResult(shadowActivity.resultCode, shadowActivity.resultIntent)
102
103 assertThat(recursiveReflectionEquals(result, value)).isTrue()
104 } finally {
105 controller.pause()
106 controller.stop()
107 controller.destroy()
108 }
109 }
110
111 /** Assert that an object can be correctly written to and parsed from an Intent. */
assertIntentEncodesCorrectlynull112 fun <I> assertIntentEncodesCorrectly(parser: IntentSerializer<I>, obj: I) {
113 val intent = Intent()
114 parser.write(intent, obj)
115 val out = parser.read(intent)
116
117 assertThat(recursiveReflectionEquals(out, obj)).isTrue()
118 }
119
120 /**
121 * Assert that a given [serializer] decodes an empty [Intent] without throwing an exception (fails
122 * lazily on property access).
123 */
assertEmptyIntentDecodingFailsLazilynull124 fun <I : Any> assertEmptyIntentDecodingFailsLazily(serializer: IntentSerializer<I>) {
125 val intent = Intent()
126 val arg = serializer.read(intent)
127 val failures =
128 arg::class
129 .memberProperties
130 .map { @Suppress("UNCHECKED_CAST") (it as KProperty1<I, *>) }
131 .map { runCatching { it.get(arg) } }
132 .filter(Result<*>::isFailure)
133 assertWithMessage("Accessing parsed properties throws lazy errors").that(failures).isNotEmpty()
134 }
135