• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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