@@ -6,22 +6,20 @@ package at.bitfire.davdroid.sync
6
6
7
7
import android.accounts.Account
8
8
import android.content.ContentResolver
9
- import android.content.Context
10
9
import android.content.SyncRequest
10
+ import android.content.SyncStatusObserver
11
11
import android.os.Bundle
12
12
import android.provider.CalendarContract
13
13
import androidx.test.filters.SdkSuppress
14
14
import at.bitfire.davdroid.sync.account.TestAccount
15
- import dagger.hilt.android.qualifiers.ApplicationContext
16
15
import dagger.hilt.android.testing.HiltAndroidRule
17
16
import dagger.hilt.android.testing.HiltAndroidTest
18
- import io.mockk.junit4.MockKRule
19
- import junit.framework.AssertionFailedError
20
17
import kotlinx.coroutines.delay
21
18
import kotlinx.coroutines.runBlocking
22
19
import kotlinx.coroutines.withTimeout
23
20
import org.junit.After
24
21
import org.junit.AfterClass
22
+ import org.junit.Assert.assertTrue
25
23
import org.junit.Before
26
24
import org.junit.BeforeClass
27
25
import org.junit.Rule
@@ -33,18 +31,11 @@ import javax.inject.Inject
33
31
import kotlin.time.Duration.Companion.seconds
34
32
35
33
@HiltAndroidTest
36
- class AndroidSyncFrameworkTest {
34
+ class AndroidSyncFrameworkTest : SyncStatusObserver {
37
35
38
36
@get:Rule
39
37
val hiltRule = HiltAndroidRule (this )
40
38
41
- @get:Rule
42
- val mockkRule = MockKRule (this )
43
-
44
- @Inject
45
- @ApplicationContext
46
- lateinit var context: Context
47
-
48
39
@Inject
49
40
lateinit var logger: Logger
50
41
@@ -68,7 +59,7 @@ class AndroidSyncFrameworkTest {
68
59
onStatusChanged(0 ) // record first entry (pending = false, active = false)
69
60
stateChangeListener = ContentResolver .addStatusChangeListener(
70
61
ContentResolver .SYNC_OBSERVER_TYPE_PENDING or ContentResolver .SYNC_OBSERVER_TYPE_ACTIVE ,
71
- ::onStatusChanged
62
+ this
72
63
)
73
64
}
74
65
@@ -129,6 +120,10 @@ class AndroidSyncFrameworkTest {
129
120
* Verifies that the given expected states match the recorded states.
130
121
*/
131
122
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
+
132
127
// We use runBlocking for these tests because it uses the default dispatcher
133
128
// which does not auto-advance virtual time and we need real system time to
134
129
// test the sync framework behavior.
@@ -143,47 +138,60 @@ class AndroidSyncFrameworkTest {
143
138
while (recordedStates.size < expectedStates.size) {
144
139
// verify already known states
145
140
if (recordedStates.isNotEmpty())
146
- assertStatesEqual(expectedStates.subList( 0 , recordedStates.size), recordedStates )
141
+ assertStatesEqual(expectedStates, recordedStates, fullMatch = false )
147
142
148
143
delay(500 ) // avoid busy-waiting
149
144
}
150
145
151
- assertStatesEqual(expectedStates, recordedStates)
146
+ assertStatesEqual(expectedStates, recordedStates, fullMatch = true )
152
147
}
153
148
}
154
149
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
+
155
154
/* *
156
- * Asserts whether [actualStates] and [expectedStates] are the same , under the condition
155
+ * Checks whether [actualStates] have matching [expectedStates], under the condition
157
156
* 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
158
163
*/
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 {
164
165
// iterate through entries
165
166
val expectedIterator = expectedStates.iterator()
166
167
for (actual in actualStates) {
167
168
if (! expectedIterator.hasNext())
168
- fail()
169
+ return false
169
170
var expected = expectedIterator.next()
170
171
171
172
// skip optional expected entries if they don't match the actual entry
172
173
while (! actual.stateEquals(expected) && expected.optional) {
173
174
if (! expectedIterator.hasNext())
174
- fail()
175
+ return false
175
176
expected = expectedIterator.next()
176
177
}
177
178
179
+ // we now have a non-optional expected state and it must match
178
180
if (! actual.stateEquals(expected))
179
- fail()
181
+ return false
180
182
}
183
+
184
+ // full match: all expected states must have been used
185
+ if (fullMatch && expectedIterator.hasNext())
186
+ return false
187
+
188
+ return true
181
189
}
182
190
183
191
184
192
// SyncStatusObserver implementation and data class
185
193
186
- fun onStatusChanged (which : Int ) {
194
+ override fun onStatusChanged (which : Int ) {
187
195
val state = State (
188
196
pending = ContentResolver .isSyncPending(account, authority),
189
197
active = ContentResolver .isSyncActive(account, authority)
0 commit comments