Skip to content

Commit b78ce50

Browse files
committed
Simplify service test pattern
* Make a simple test lifecycle registry, instead of creating empty testactivity * Remove use of `runBlockingTest`: according to Kotlin/kotlinx.coroutines#1222, if the test results in coroutines being finished on other threads, it's possible to receive "This job has not yet completed" exceptions, even though the jobs were properly terminated. Since we don't need the delay-skipping properties of `runBlockingTest`, I think it's OK to simply using `runBlocking`, instead.
1 parent a695797 commit b78ce50

File tree

5 files changed

+109
-141
lines changed

5 files changed

+109
-141
lines changed

javatests/arcs/android/host/BUILD

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ licenses(["notice"])
99

1010
package(default_visibility = ["//visibility:public"])
1111

12+
kt_android_library(
13+
name = "test_app",
14+
testonly = 1,
15+
srcs = ["TestActivity.kt"],
16+
manifest = ":AndroidManifest.xml",
17+
deps = [
18+
"//third_party/java/androidx/appcompat",
19+
],
20+
)
21+
1222
arcs_kt_android_test_suite(
1323
name = "host",
1424
srcs = glob(["*Test.kt"]),
@@ -17,6 +27,7 @@ arcs_kt_android_test_suite(
1727
deps = [
1828
":schemas",
1929
":services",
30+
":test_app",
2031
"//java/arcs/android/crdt",
2132
"//java/arcs/android/host",
2233
"//java/arcs/android/sdk/host",
@@ -42,7 +53,6 @@ arcs_kt_android_test_suite(
4253
"//java/arcs/sdk/android/storage",
4354
"//java/arcs/sdk/android/storage/service",
4455
"//java/arcs/sdk/android/storage/service/testutil",
45-
"//javatests/arcs/android/storage/handle:test_app",
4656
"//javatests/arcs/core/allocator:allocator-test-util",
4757
"//third_party/android/androidx_test/core",
4858
"//third_party/android/androidx_test/ext/junit",

javatests/arcs/android/storage/handle/AndroidHandleManagerTest.kt

+98-116
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package arcs.android.storage.handle
22

33
import android.app.Application
44
import androidx.lifecycle.Lifecycle
5+
import androidx.lifecycle.LifecycleOwner
6+
import androidx.lifecycle.LifecycleRegistry
57
import androidx.test.core.app.ActivityScenario
68
import androidx.test.core.app.ApplicationProvider
79
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,9 +28,10 @@ import arcs.sdk.android.storage.service.DefaultConnectionFactory
2628
import arcs.sdk.android.storage.service.testutil.TestBindingDelegate
2729
import com.google.common.truth.Truth.assertThat
2830
import com.nhaarman.mockitokotlin2.mock
31+
import kotlinx.coroutines.Dispatchers
32+
import kotlinx.coroutines.delay
2933
import kotlinx.coroutines.launch
3034
import kotlinx.coroutines.runBlocking
31-
import kotlinx.coroutines.test.TestCoroutineScope
3235
import kotlinx.coroutines.test.runBlockingTest
3336
import org.junit.Before
3437
import org.junit.Test
@@ -39,27 +42,32 @@ import org.mockito.Mockito.verify
3942

4043
@Suppress("EXPERIMENTAL_API_USAGE")
4144
@RunWith(AndroidJUnit4::class)
42-
class AndroidHandleManagerTest {
45+
class AndroidHandleManagerTest : LifecycleOwner {
46+
private lateinit var lifecycle: LifecycleRegistry
47+
override fun getLifecycle() = lifecycle
48+
4349
private lateinit var app: Application
4450

51+
private lateinit var handleManager: HandleManager
52+
4553
val entity1 = RawEntity(
4654
"entity1",
47-
singletons=mapOf(
55+
singletons = mapOf(
4856
"name" to "Jason".toReferencable(),
4957
"age" to 21.toReferencable(),
5058
"is_cool" to false.toReferencable()
5159
),
52-
collections=emptyMap()
60+
collections = emptyMap()
5361
)
5462

5563
val entity2 = RawEntity(
5664
"entity2",
57-
singletons=mapOf(
65+
singletons = mapOf(
5866
"name" to "Jason".toReferencable(),
5967
"age" to 22.toReferencable(),
6068
"is_cool" to true.toReferencable()
6169
),
62-
collections=emptyMap()
70+
collections = emptyMap()
6371
)
6472

6573
private val schema = Schema(
@@ -88,140 +96,114 @@ class AndroidHandleManagerTest {
8896
@Before
8997
fun setUp() {
9098
RamDisk.clear()
99+
lifecycle = LifecycleRegistry(this).apply {
100+
setCurrentState(Lifecycle.State.CREATED)
101+
setCurrentState(Lifecycle.State.STARTED)
102+
setCurrentState(Lifecycle.State.RESUMED)
103+
}
91104
app = ApplicationProvider.getApplicationContext()
92-
app.setTheme(R.style.Theme_AppCompat);
93105

94106
// Initialize WorkManager for instrumentation tests.
95107
WorkManagerTestInitHelper.initializeTestWorkManager(app)
96-
}
97108

98-
fun handleManagerTest(
99-
block: suspend TestCoroutineScope.(HandleManager) -> Unit
100-
) = runBlockingTest {
101-
val scenario = ActivityScenario.launch(TestActivity::class.java)
102-
103-
scenario.moveToState(Lifecycle.State.STARTED)
104-
105-
val activityJob = launch {
106-
scenario.onActivity { activity ->
107-
val hf = AndroidHandleManager(
108-
lifecycle = activity.lifecycle,
109-
context = activity,
110-
connectionFactory = DefaultConnectionFactory(activity, TestBindingDelegate(app)),
111-
coroutineContext = coroutineContext
112-
)
113-
runBlocking {
114-
this@runBlockingTest.block(hf)
115-
}
116-
scenario.close()
117-
}
118-
}
119-
120-
activityJob.join()
109+
handleManager = AndroidHandleManager(
110+
lifecycle = lifecycle,
111+
context = app,
112+
connectionFactory = DefaultConnectionFactory(app, TestBindingDelegate(app))
113+
)
121114
}
122115

123116
@Test
124-
fun testCreateSingletonHandle() = runBlockingTest {
125-
handleManagerTest { hm ->
126-
val singletonHandle = hm.singletonHandle(singletonKey, schema)
127-
singletonHandle.store(entity1)
128-
129-
// Now read back from a different handle
130-
val readbackHandle = hm.singletonHandle(singletonKey, schema)
131-
val readBack = readbackHandle.fetch()
132-
assertThat(readBack).isEqualTo(entity1)
133-
}
117+
fun singleton_writeAndReadback() = runBlocking {
118+
val singletonHandle = handleManager.singletonHandle(singletonKey, schema)
119+
singletonHandle.store(entity1)
120+
121+
// Now read back from a different handle
122+
val readbackHandle = handleManager.singletonHandle(singletonKey, schema)
123+
val readBack = readbackHandle.fetch()
124+
assertThat(readBack).isEqualTo(entity1)
125+
134126
}
135127

136128
@Test
137-
fun testCreateSetHandle() = runBlockingTest {
138-
handleManagerTest { hm ->
139-
val setHandle = hm.setHandle(setKey, schema)
140-
setHandle.store(entity1)
141-
setHandle.store(entity2)
142-
143-
// Now read back from a different handle
144-
val secondHandle = hm.setHandle(setKey, schema)
145-
val readBack = secondHandle.fetchAll()
146-
assertThat(readBack).containsExactly(entity1, entity2)
147-
}
129+
fun set_writeAndReadback() = runBlocking<Unit> {
130+
val setHandle = handleManager.setHandle(setKey, schema)
131+
setHandle.store(entity1)
132+
setHandle.store(entity2)
133+
134+
// Now read back from a different handle
135+
val secondHandle = handleManager.setHandle(setKey, schema)
136+
val readBack = secondHandle.fetchAll()
137+
assertThat(readBack).containsExactly(entity1, entity2)
148138
}
149139

150140
private fun testMapForKey(key: StorageKey) = VersionMap(key.toKeyString() to 1)
151141

152142
@Test
153-
fun testSetHandleOnUpdate() = runBlockingTest {
154-
handleManagerTest { hm ->
155-
val testCallback1 = mock<SetCallbacks<RawEntity>>()
156-
val testCallback2 = mock<SetCallbacks<RawEntity>>()
157-
val firstHandle = hm.setHandle(setKey, schema, testCallback1)
158-
val secondHandle = hm.setHandle(setKey, schema, testCallback2)
159-
160-
val expectedAdd = CrdtSet.Operation.Add(
161-
setKey.toKeyString(),
162-
testMapForKey(setKey),
163-
entity1
164-
)
165-
secondHandle.store(entity1)
166-
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedAdd)
167-
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedAdd)
168-
169-
firstHandle.remove(entity1)
170-
val expectedRemove = CrdtSet.Operation.Remove(
171-
setKey.toKeyString(),
172-
testMapForKey(setKey),
173-
entity1
174-
)
175-
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedRemove)
176-
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedRemove)
177-
}
143+
fun set_onHandleUpdate() = runBlocking<Unit> {
144+
val testCallback1 = mock<SetCallbacks<RawEntity>>()
145+
val testCallback2 = mock<SetCallbacks<RawEntity>>()
146+
val firstHandle = handleManager.setHandle(setKey, schema, testCallback1)
147+
val secondHandle = handleManager.setHandle(setKey, schema, testCallback2)
148+
149+
val expectedAdd = CrdtSet.Operation.Add(
150+
setKey.toKeyString(),
151+
testMapForKey(setKey),
152+
entity1
153+
)
154+
secondHandle.store(entity1)
155+
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedAdd)
156+
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedAdd)
157+
158+
firstHandle.remove(entity1)
159+
val expectedRemove = CrdtSet.Operation.Remove(
160+
setKey.toKeyString(),
161+
testMapForKey(setKey),
162+
entity1
163+
)
164+
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedRemove)
165+
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedRemove)
178166
}
179167

180168
@Test
181-
fun testSingletonHandleOnUpdate() = runBlockingTest {
182-
handleManagerTest { hm ->
183-
val testCallback1 = mock<SingletonCallbacks<RawEntity>>()
184-
val testCallback2 = mock<SingletonCallbacks<RawEntity>>()
185-
val firstHandle = hm.singletonHandle(singletonKey, schema, testCallback1)
186-
val secondHandle = hm.singletonHandle(singletonKey, schema, testCallback2)
187-
secondHandle.store(entity1)
188-
val expectedAdd = CrdtSingleton.Operation.Update(
189-
singletonKey.toKeyString(),
190-
testMapForKey(singletonKey),
191-
entity1
192-
)
193-
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedAdd)
194-
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedAdd)
195-
firstHandle.clear()
196-
197-
val expectedRemove = CrdtSingleton.Operation.Clear<RawEntity>(
198-
singletonKey.toKeyString(),
199-
testMapForKey(singletonKey)
200-
)
201-
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedRemove)
202-
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedRemove)
203-
}
169+
fun singleton_OnHandleUpdate() = runBlocking<Unit> {
170+
val testCallback1 = mock<SingletonCallbacks<RawEntity>>()
171+
val testCallback2 = mock<SingletonCallbacks<RawEntity>>()
172+
val firstHandle = handleManager.singletonHandle(singletonKey, schema, testCallback1)
173+
val secondHandle = handleManager.singletonHandle(singletonKey, schema, testCallback2)
174+
secondHandle.store(entity1)
175+
val expectedAdd = CrdtSingleton.Operation.Update(
176+
singletonKey.toKeyString(),
177+
testMapForKey(singletonKey),
178+
entity1
179+
)
180+
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedAdd)
181+
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedAdd)
182+
firstHandle.clear()
183+
184+
val expectedRemove = CrdtSingleton.Operation.Clear<RawEntity>(
185+
singletonKey.toKeyString(),
186+
testMapForKey(singletonKey)
187+
)
188+
verify(testCallback1, times(1)).onUpdate(firstHandle, expectedRemove)
189+
verify(testCallback2, times(1)).onUpdate(secondHandle, expectedRemove)
204190
}
205191

206192
@Test
207-
fun testSetSyncOnRegister() = runBlockingTest {
208-
handleManagerTest { hm ->
209-
val testCallback = mock<SetCallbacks<RawEntity>>()
210-
val firstHandle = hm.setHandle(setKey, schema, testCallback)
211-
verify(testCallback, times(1)).onSync(firstHandle)
212-
firstHandle.fetchAll()
213-
verify(testCallback, times(1)).onSync(firstHandle)
214-
}
193+
fun set_syncOnRegister() = runBlocking<Unit> {
194+
val testCallback = mock<SetCallbacks<RawEntity>>()
195+
val firstHandle = handleManager.setHandle(setKey, schema, testCallback)
196+
verify(testCallback, times(1)).onSync(firstHandle)
197+
firstHandle.fetchAll()
198+
verify(testCallback, times(1)).onSync(firstHandle)
215199
}
216200

217201
@Test
218-
fun testSingletonSyncOnRegister() = runBlockingTest {
219-
handleManagerTest { hm ->
220-
val testCallback = mock<SingletonCallbacks<RawEntity>>()
221-
val firstHandle = hm.singletonHandle(setKey, schema, testCallback)
222-
verify(testCallback, times(1)).onSync(firstHandle)
223-
firstHandle.fetch()
224-
verify(testCallback, times(1)).onSync(firstHandle)
225-
}
202+
fun testSingletonSyncOnRegister() = runBlocking<Unit> {
203+
val testCallback = mock<SingletonCallbacks<RawEntity>>()
204+
val firstHandle = handleManager.singletonHandle(setKey, schema, testCallback)
205+
verify(testCallback, times(1)).onSync(firstHandle)
206+
firstHandle.fetch()
207+
verify(testCallback, times(1)).onSync(firstHandle)
226208
}
227209
}

javatests/arcs/android/storage/handle/AndroidManifest.xml

-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@
1616

1717
<application>
1818
<service android:name="arcs.sdk.android.service.StorageService" android:exported="false"/>
19-
<activity android:name="arcs.android.storage.handle.TestActivity" >
20-
<intent-filter>
21-
<action android:name="android.intent.action.MAIN"/>
22-
<category android:name="android.intent.category.LAUNCHER"/>
23-
</intent-filter>
24-
</activity>
2519
</application>
2620

2721
</manifest>

javatests/arcs/android/storage/handle/BUILD

-13
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,12 @@ licenses(["notice"])
88

99
package(default_visibility = ["//visibility:public"])
1010

11-
kt_android_library(
12-
name = "test_app",
13-
testonly = 1,
14-
srcs = ["TestActivity.kt"],
15-
manifest = ":AndroidManifest.xml",
16-
deps = [
17-
"//third_party/java/androidx/appcompat",
18-
],
19-
)
20-
2111
arcs_kt_android_test_suite(
2212
name = "handle",
2313
srcs = glob(["*Test.kt"]),
2414
manifest = "AndroidManifest.xml",
2515
package = "arcs.android.storage.handle",
2616
deps = [
27-
":test_app",
2817
"//java/arcs/android/crdt",
2918
"//java/arcs/android/storage",
3019
"//java/arcs/android/storage/handle",
@@ -44,14 +33,12 @@ arcs_kt_android_test_suite(
4433
"//java/arcs/sdk/android/storage/service/testutil",
4534
"//third_party/android/androidx_test/core",
4635
"//third_party/android/androidx_test/ext/junit",
47-
"//third_party/java/androidx/appcompat",
4836
"//third_party/java/androidx/work:testing",
4937
"//third_party/java/junit:junit-android",
5038
"//third_party/java/mockito:mockito-android",
5139
"//third_party/java/robolectric",
5240
"//third_party/java/truth:truth-android",
5341
"//third_party/kotlin/kotlinx_coroutines",
54-
"//third_party/kotlin/kotlinx_coroutines:kotlinx_coroutines_test",
5542
"//third_party/kotlin/mockito_kotlin:mockito_kotlin-android",
5643
],
5744
)

javatests/arcs/android/storage/handle/TestActivity.kt

-5
This file was deleted.

0 commit comments

Comments
 (0)