Skip to content

Commit 00523d9

Browse files
authored
Fix state matching logic in AndroidSyncFrameworkTest (#1708)
* Fix state matching logic in AndroidSyncFrameworkTest - Add `fullMatch` parameter to control whether all expected states must be present * Ensure non-optional expected state matches actual state * Remove unused rule / variable * Adapt test - Update `onStatusChanged` to override the interface method. - Replace custom assertion with `assertTrue` for state comparison.
1 parent 53d338d commit 00523d9

File tree

1 file changed

+33
-25
lines changed

1 file changed

+33
-25
lines changed

app/src/androidTest/kotlin/at/bitfire/davdroid/sync/AndroidSyncFrameworkTest.kt

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,20 @@ package at.bitfire.davdroid.sync
66

77
import android.accounts.Account
88
import android.content.ContentResolver
9-
import android.content.Context
109
import android.content.SyncRequest
10+
import android.content.SyncStatusObserver
1111
import android.os.Bundle
1212
import android.provider.CalendarContract
1313
import androidx.test.filters.SdkSuppress
1414
import at.bitfire.davdroid.sync.account.TestAccount
15-
import dagger.hilt.android.qualifiers.ApplicationContext
1615
import dagger.hilt.android.testing.HiltAndroidRule
1716
import dagger.hilt.android.testing.HiltAndroidTest
18-
import io.mockk.junit4.MockKRule
19-
import junit.framework.AssertionFailedError
2017
import kotlinx.coroutines.delay
2118
import kotlinx.coroutines.runBlocking
2219
import kotlinx.coroutines.withTimeout
2320
import org.junit.After
2421
import org.junit.AfterClass
22+
import org.junit.Assert.assertTrue
2523
import org.junit.Before
2624
import org.junit.BeforeClass
2725
import org.junit.Rule
@@ -33,18 +31,11 @@ import javax.inject.Inject
3331
import kotlin.time.Duration.Companion.seconds
3432

3533
@HiltAndroidTest
36-
class AndroidSyncFrameworkTest {
34+
class AndroidSyncFrameworkTest: SyncStatusObserver {
3735

3836
@get:Rule
3937
val hiltRule = HiltAndroidRule(this)
4038

41-
@get:Rule
42-
val mockkRule = MockKRule(this)
43-
44-
@Inject
45-
@ApplicationContext
46-
lateinit var context: Context
47-
4839
@Inject
4940
lateinit var logger: Logger
5041

@@ -68,7 +59,7 @@ class AndroidSyncFrameworkTest {
6859
onStatusChanged(0) // record first entry (pending = false, active = false)
6960
stateChangeListener = ContentResolver.addStatusChangeListener(
7061
ContentResolver.SYNC_OBSERVER_TYPE_PENDING or ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE,
71-
::onStatusChanged
62+
this
7263
)
7364
}
7465

@@ -129,6 +120,10 @@ class AndroidSyncFrameworkTest {
129120
* Verifies that the given expected states match the recorded states.
130121
*/
131122
private fun verifySyncStates(expectedStates: List<State>) = runBlocking {
123+
// Verify that last state is non-optional.
124+
if (expectedStates.last().optional)
125+
throw IllegalArgumentException("Last expected state must not be optional")
126+
132127
// We use runBlocking for these tests because it uses the default dispatcher
133128
// which does not auto-advance virtual time and we need real system time to
134129
// test the sync framework behavior.
@@ -143,47 +138,60 @@ class AndroidSyncFrameworkTest {
143138
while (recordedStates.size < expectedStates.size) {
144139
// verify already known states
145140
if (recordedStates.isNotEmpty())
146-
assertStatesEqual(expectedStates.subList(0, recordedStates.size), recordedStates)
141+
assertStatesEqual(expectedStates, recordedStates, fullMatch = false)
147142

148143
delay(500) // avoid busy-waiting
149144
}
150145

151-
assertStatesEqual(expectedStates, recordedStates)
146+
assertStatesEqual(expectedStates, recordedStates, fullMatch = true)
152147
}
153148
}
154149

150+
private fun assertStatesEqual(expectedStates: List<State>, actualStates: List<State>, fullMatch: Boolean) {
151+
assertTrue("Expected states=$expectedStates, actual=$actualStates", statesMatch(expectedStates, actualStates, fullMatch))
152+
}
153+
155154
/**
156-
* Asserts whether [actualStates] and [expectedStates] are the same, under the condition
155+
* Checks whether [actualStates] have matching [expectedStates], under the condition
157156
* that expected states with the [State.optional] flag can be skipped.
157+
*
158+
* Note: When [fullMatch] is not set, this method can return _true_ even if not all expected states are used.
159+
*
160+
* @param expectedStates expected states (can include optional states which don't have to be present in actual states)
161+
* @param actualStates actual states
162+
* @param fullMatch whether all non-optional expected states must be present in actual states
158163
*/
159-
private fun assertStatesEqual(expectedStates: List<State>, actualStates: List<State>) {
160-
fun fail() {
161-
throw AssertionFailedError("Expected states=$expectedStates, actual=$actualStates")
162-
}
163-
164+
private fun statesMatch(expectedStates: List<State>, actualStates: List<State>, fullMatch: Boolean): Boolean {
164165
// iterate through entries
165166
val expectedIterator = expectedStates.iterator()
166167
for (actual in actualStates) {
167168
if (!expectedIterator.hasNext())
168-
fail()
169+
return false
169170
var expected = expectedIterator.next()
170171

171172
// skip optional expected entries if they don't match the actual entry
172173
while (!actual.stateEquals(expected) && expected.optional) {
173174
if (!expectedIterator.hasNext())
174-
fail()
175+
return false
175176
expected = expectedIterator.next()
176177
}
177178

179+
// we now have a non-optional expected state and it must match
178180
if (!actual.stateEquals(expected))
179-
fail()
181+
return false
180182
}
183+
184+
// full match: all expected states must have been used
185+
if (fullMatch && expectedIterator.hasNext())
186+
return false
187+
188+
return true
181189
}
182190

183191

184192
// SyncStatusObserver implementation and data class
185193

186-
fun onStatusChanged(which: Int) {
194+
override fun onStatusChanged(which: Int) {
187195
val state = State(
188196
pending = ContentResolver.isSyncPending(account, authority),
189197
active = ContentResolver.isSyncActive(account, authority)

0 commit comments

Comments
 (0)