Skip to content

Commit 2aa7d15

Browse files
committed
Add some failing unit tests
1 parent bd0651f commit 2aa7d15

3 files changed

Lines changed: 239 additions & 36 deletions

File tree

app/src/main/java/org/akanework/gramophone/logic/utils/flows/IncrementalFlows.kt

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,21 @@ import kotlin.math.abs
3737
import kotlin.math.min
3838

3939
sealed class IncrementalList<T>(val after: List<T>) {
40-
class Begin<T>(after: List<T>) : IncrementalList<T>(after)
41-
class Insert<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after)
42-
class Remove<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after)
43-
class Move<T>(val pos: Int, val count: Int, val outPos: Int, after: List<T>) : IncrementalList<T>(after)
44-
class Update<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after)
40+
class Begin<T>(after: List<T>) : IncrementalList<T>(after) {
41+
override fun toString() = "Begin()"
42+
}
43+
class Insert<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after) {
44+
override fun toString() = "Insert(pos=$pos, count=$count)"
45+
}
46+
class Remove<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after) {
47+
override fun toString() = "Remove(pos=$pos, count=$count)"
48+
}
49+
class Move<T>(val pos: Int, val count: Int, val outPos: Int, after: List<T>) : IncrementalList<T>(after) {
50+
override fun toString() = "Move(pos=$pos, count=$count, outPos=$outPos)"
51+
}
52+
class Update<T>(val pos: Int, val count: Int, after: List<T>) : IncrementalList<T>(after) {
53+
override fun toString() = "Update(pos=$pos, count=$count)"
54+
}
4555
}
4656

4757
sealed class IncrementalMap<T, R>(val after: Map<T, R>) {
@@ -80,7 +90,7 @@ inline fun <T, R> Flow<IncrementalList<T>>.flatMapIncremental(
8090
totalStart += new[i].size
8191
}
8292
newFlat = new.flatMap { it }
83-
emit(IncrementalList.Insert(totalSize, totalStart, newFlat))
93+
emit(IncrementalList.Insert(totalStart, totalSize, newFlat))
8494
}
8595
}
8696
command is IncrementalList.Move -> {
@@ -89,13 +99,8 @@ inline fun <T, R> Flow<IncrementalList<T>>.flatMapIncremental(
8999
repeat(command.count) { _ ->
90100
totalSize += new.removeAt(command.pos).size
91101
}
92-
val insertPos = if (command.outPos > command.pos) {
93-
command.outPos - command.count
94-
} else {
95-
command.outPos
96-
}
97-
for (i in insertPos..<insertPos+command.count) {
98-
new.add(i, last!![i - insertPos + command.pos])
102+
for (i in command.outPos..<command.outPos+command.count) {
103+
new.add(i, last!![i - command.outPos + command.pos])
99104
}
100105
if (totalSize > 0) {
101106
var totalStart = 0
@@ -122,7 +127,7 @@ inline fun <T, R> Flow<IncrementalList<T>>.flatMapIncremental(
122127
totalStart += new[i].size
123128
}
124129
newFlat = new.flatMap { it }
125-
emit(IncrementalList.Remove(totalSize, totalStart, newFlat))
130+
emit(IncrementalList.Remove(totalStart, totalSize, newFlat))
126131
}
127132
}
128133
command is IncrementalList.Update -> {
@@ -199,13 +204,8 @@ inline fun <T, R> Flow<IncrementalList<T>>.mapIncremental(
199204
repeat(command.count) { _ ->
200205
new.removeAt(command.pos)
201206
}
202-
val insertPos = if (command.outPos > command.pos) {
203-
command.outPos - command.count
204-
} else {
205-
command.outPos
206-
}
207-
for (i in insertPos..<insertPos+command.count) {
208-
new.add(i, last!![i - insertPos + command.pos])
207+
for (i in command.outPos..<command.outPos+command.count) {
208+
new.add(i, last!![i - command.outPos + command.pos])
209209
}
210210
emit(IncrementalList.Move(command.pos, command.count, command.outPos, new))
211211
}
@@ -286,15 +286,9 @@ inline fun <T, R> Flow<IncrementalList<T>>.groupByIncremental(
286286
repeat(it.count) { _ ->
287287
keys.add(keyCache.removeAt(it.pos))
288288
}
289-
val insertPos = if (it.outPos > it.pos) {
290-
it.outPos - it.count
291-
} else {
292-
it.outPos
293-
}
294-
for (i in insertPos..<insertPos+it.count) {
295-
keyCache.add(i, keys[i - insertPos])
289+
for (i in it.outPos..<it.outPos+it.count) {
290+
keyCache.add(i, keys[i - it.outPos])
296291
}
297-
// TODO indexes probably are wrong
298292
for (i in it.pos..<it.pos + it.count) {
299293
val outPos = it.outPos - it.pos + i
300294
val item = it.after[outPos]
@@ -877,8 +871,10 @@ val albumsFlow: SharedFlow<IncrementalList<Album2>> = rawAlbumsFlow
877871
findBestCover(File(firstFolder))?.toUriCompat()
878872
} else fallbackCover
879873
}
880-
} else flowOf(if (albumId != null)
881-
ContentUris.withAppendedId(Constants.baseAlbumCoverUri, albumId) else fallbackCover)
874+
} else flowOf(
875+
if (albumId != null)
876+
ContentUris.withAppendedId(Constants.baseAlbumCoverUri, albumId) else fallbackCover
877+
)
882878
val artistIdFlow = if (artist?.second != null) flowOf(artist.second) else if (artist != null)
883879
readerFlow.map { it.canonicalArtistIdMap[artist.first] }.distinctUntilChanged() else flowOf(null)
884880
albumArtFlow.combine(artistIdFlow) { cover, artistId ->
@@ -913,7 +909,7 @@ private val artistsWithoutSongsFlow = albumsForArtistFlow
913909
}
914910
.flattenIncremental()
915911
val artistFlow: SharedFlow<IncrementalList<Artist2>> = rawArtistFlow
916-
.mapIncremental { artistId, songs -> // TODO missing artists that only have albums?
912+
.mapIncremental { artistId, songs ->
917913
val songList = songs.after
918914
val title = songList.first().mediaMetadata.artist?.toString()
919915
val cover = songList.first().mediaMetadata.artworkUri

app/src/main/java/org/akanework/gramophone/ui/AudioPreviewActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class AudioPreviewActivity : AppCompatActivity(), View.OnClickListener {
317317
} catch (e: Exception) {
318318
if (e is SecurityException || e.message == "Provider for this Uri is not supported."
319319
|| e.message?.startsWith("Invalid URI: ") == true
320-
)
320+
|| e.message?.contains("Missing file for") == true)
321321
Log.w(TAG, e.javaClass.name + ": " + e.message)
322322
else
323323
Log.e(TAG, Log.getStackTraceString(e))

app/src/test/java/org/akanework/gramophone/PauseableFlowsTest.kt

Lines changed: 210 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@ package org.akanework.gramophone
22

33
import kotlinx.coroutines.Dispatchers
44
import kotlinx.coroutines.delay
5+
import kotlinx.coroutines.flow.Flow
56
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.flow.collect
68
import kotlinx.coroutines.flow.flow
9+
import kotlinx.coroutines.flow.flowOf
10+
import kotlinx.coroutines.flow.map
11+
import kotlinx.coroutines.flow.onEach
12+
import kotlinx.coroutines.flow.toCollection
713
import kotlinx.coroutines.launch
814
import kotlinx.coroutines.runBlocking
915
import org.akanework.gramophone.logic.utils.flows.IncrementalList
16+
import org.akanework.gramophone.logic.utils.flows.IncrementalMap
1017
import org.akanework.gramophone.logic.utils.flows.PauseManager
1118
import org.akanework.gramophone.logic.utils.flows.conflateAndBlockWhenPaused
19+
import org.akanework.gramophone.logic.utils.flows.filterIncremental
20+
import org.akanework.gramophone.logic.utils.flows.flatMapIncremental
21+
import org.akanework.gramophone.logic.utils.flows.flattenIncremental
22+
import org.akanework.gramophone.logic.utils.flows.forKey
23+
import org.akanework.gramophone.logic.utils.flows.groupByIncremental
24+
import org.akanework.gramophone.logic.utils.flows.mapIncremental
1225
import org.junit.Assert.assertEquals
26+
import org.junit.Assert.assertFalse
27+
import org.junit.Assert.assertTrue
1328
import org.junit.Test
1429

1530
class PauseableFlowsTest {
@@ -60,9 +75,201 @@ class PauseableFlowsTest {
6075

6176
@Test
6277
fun incrementalFlows() {
63-
val source = flow {
64-
emit(IncrementalList.Begin(listOf(1, 2, 3)))
65-
emit(IncrementalList.Insert(1, 2, listOf(1, 999, 2, 3)))
78+
runBlocking {
79+
var countEmitted = 0
80+
var countMapped = 0
81+
var countFiltered = 0
82+
val source = flow {
83+
emit(IncrementalList.Begin(listOf(1, 2, 3)))
84+
emit(IncrementalList.Insert(1, 2, listOf(1, 15, 10, 2, 3)))
85+
emit(IncrementalList.Insert(1, 1, listOf(1, 999, 15, 10, 2, 3)))
86+
emit(IncrementalList.Move(1, 1, 2, listOf(1, 15, 999, 10, 2, 3)))
87+
emit(IncrementalList.Move(1, 1, 5, listOf(1, 999, 10, 2, 3, 15)))
88+
emit(IncrementalList.Move(2, 3, 0, listOf(10, 2, 3, 1, 999, 15)))
89+
emit(IncrementalList.Remove(1, 1, listOf(10, 3, 1, 999, 15)))
90+
emit(IncrementalList.Update(1, 1, listOf(10, 5, 1, 999, 15)))
91+
}
92+
.assertContractNotViolated("init")
93+
.onEach { countEmitted++ }
94+
.mapIncremental { it + 1 }
95+
.assertContractNotViolated("after map")
96+
.onEach { countMapped++ }
97+
.filterIncremental { it < 100 }
98+
.assertContractNotViolated("after filter")
99+
.onEach { countFiltered++ }
100+
.flatMapIncremental { if (it % 2 == 0) listOf(it, it) else emptyList() }
101+
.assertContractNotViolated("after flatMap")
102+
val out = ArrayList<IncrementalList<Int>>()
103+
source.toCollection(out)
104+
assertEquals(8, countEmitted)
105+
assertEquals(8, countMapped)
106+
assertEquals(6, countFiltered)
107+
assertEquals(5, out.size)
108+
assertTrue(out[0] is IncrementalList.Begin)
109+
assertTrue(out[1] is IncrementalList.Insert)
110+
assertTrue(out[2] is IncrementalList.Move)
111+
assertTrue(out[3] is IncrementalList.Move)
112+
assertTrue(out[4] is IncrementalList.Update)
113+
assertEquals(listOf(2, 2, 16, 16, 4, 4), out[1].after)
114+
assertEquals(listOf(4, 4, 2, 2, 16, 16), out[3].after)
115+
assertEquals(listOf(6, 6, 2, 2, 16, 16), out[4].after)
116+
}
117+
}
118+
119+
@Test
120+
fun incrementalFlowsGroupBy() {
121+
runBlocking {
122+
var countEmitted = 0
123+
var countMapped = 0
124+
var countFiltered = 0
125+
val source = flow {
126+
emit(IncrementalList.Begin(listOf(1, 2, 3)))
127+
emit(IncrementalList.Insert(1, 2, listOf(1, 15, 10, 2, 3)))
128+
emit(IncrementalList.Insert(1, 1, listOf(1, 999, 15, 10, 2, 3)))
129+
emit(IncrementalList.Move(1, 1, 2, listOf(1, 15, 999, 10, 2, 3)))
130+
emit(IncrementalList.Move(1, 1, 5, listOf(1, 999, 10, 2, 3, 15)))
131+
emit(IncrementalList.Move(2, 3, 0, listOf(10, 2, 3, 1, 999, 15)))
132+
emit(IncrementalList.Remove(1, 1, listOf(10, 3, 1, 999, 15)))
133+
emit(IncrementalList.Update(1, 1, listOf(10, 5, 1, 999, 15)))
134+
}
135+
.assertContractNotViolated("init")
136+
.onEach { countEmitted++ }
137+
.mapIncremental { it + 1 }
138+
.assertContractNotViolated("after map")
139+
.onEach { countMapped++ }
140+
.filterIncremental { it < 100 }
141+
.assertContractNotViolated("after filter")
142+
.onEach { countFiltered++ }
143+
.groupByIncremental { it % 2 }
144+
.assertContractNotViolated("after groupBy")
145+
.mapIncremental { a, b -> flowOf(b) }
146+
.assertContractNotViolated("after mapIncremental")
147+
.flattenIncremental()
148+
.assertContractNotViolated("after flattenIncremental")
149+
.forKey(1)
150+
.map { it!! }
151+
.assertContractNotViolated("after forKey")
152+
source.collect()
153+
}
154+
}
155+
156+
157+
@Test
158+
fun incrementalFlowsGroupBy2() {
159+
runBlocking {
160+
var countEmitted = 0
161+
var countMapped = 0
162+
var countFiltered = 0
163+
val source = flow {
164+
emit(IncrementalList.Begin(listOf(1, 2, 3)))
165+
emit(IncrementalList.Insert(1, 2, listOf(1, 15, 10, 2, 3)))
166+
emit(IncrementalList.Insert(1, 1, listOf(1, 999, 15, 10, 2, 3)))
167+
emit(IncrementalList.Move(1, 1, 2, listOf(1, 15, 999, 10, 2, 3)))
168+
emit(IncrementalList.Move(1, 1, 5, listOf(1, 999, 10, 2, 3, 15)))
169+
emit(IncrementalList.Move(2, 3, 0, listOf(10, 2, 3, 1, 999, 15)))
170+
emit(IncrementalList.Remove(1, 1, listOf(10, 3, 1, 999, 15)))
171+
emit(IncrementalList.Update(1, 1, listOf(10, 5, 1, 999, 15)))
172+
}
173+
.assertContractNotViolated("init")
174+
.onEach { countEmitted++ }
175+
.mapIncremental { it + 1 }
176+
.assertContractNotViolated("after map")
177+
.onEach { countMapped++ }
178+
.filterIncremental { it < 100 }
179+
.assertContractNotViolated("after filter")
180+
.onEach { countFiltered++ }
181+
.groupByIncremental { it % 2 }
182+
.assertContractNotViolated("after groupBy")
183+
.forKey(1)
184+
.map { it!! }
185+
.assertContractNotViolated("after forKey")
186+
source.collect()
187+
}
188+
}
189+
190+
fun <T> Flow<IncrementalList<T>>.assertContractNotViolated(tag: String) = flow {
191+
var cache: IncrementalList<T>? = null
192+
collect {
193+
when {
194+
it is IncrementalList.Begin || cache == null -> {
195+
// nothing to check
196+
}
197+
it is IncrementalList.Insert -> {
198+
var new = ArrayList(cache!!.after)
199+
for (i in it.pos..<it.pos+it.count) {
200+
new.add(i, it.after[i])
201+
}
202+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
203+
}
204+
it is IncrementalList.Move -> {
205+
var new = ArrayList(cache!!.after)
206+
var removed = ArrayList<T>(it.count)
207+
repeat(it.count) { _ ->
208+
removed.add(new.removeAt(it.pos))
209+
}
210+
for (i in it.outPos..<it.outPos+it.count) {
211+
new.add(i, removed[i - it.outPos])
212+
}
213+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
214+
}
215+
it is IncrementalList.Remove -> {
216+
var new = ArrayList(cache!!.after)
217+
repeat(it.count) { _ ->
218+
new.removeAt(it.pos)
219+
}
220+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
221+
}
222+
it is IncrementalList.Update -> {
223+
var new = ArrayList(cache!!.after)
224+
for (i in it.pos..<it.pos+it.count) {
225+
new[i] = it.after[i]
226+
}
227+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
228+
}
229+
else -> throw IllegalArgumentException("unknown command?")
230+
}
231+
emit(it)
232+
cache = it
233+
}
234+
}
235+
236+
@JvmName("assertContractNotViolatedMap")
237+
fun <T, R> Flow<IncrementalMap<T, R>>.assertContractNotViolated(tag: String) = flow {
238+
var cache: IncrementalMap<T, R>? = null
239+
collect {
240+
when {
241+
it is IncrementalMap.Begin || cache == null -> {
242+
// nothing to check
243+
}
244+
it is IncrementalMap.Insert -> {
245+
var new = HashMap(cache!!.after)
246+
assertFalse(new.contains(it.key))
247+
new[it.key] = it.after[it.key]
248+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
249+
}
250+
it is IncrementalMap.Move -> {
251+
var new = HashMap(cache!!.after)
252+
assertTrue(new.contains(it.key))
253+
assertFalse(new.contains(it.outKey))
254+
new[it.outKey] = new.remove(it.key)
255+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
256+
}
257+
it is IncrementalMap.Remove -> {
258+
var new = HashMap(cache!!.after)
259+
assertTrue(new.contains(it.key))
260+
new.remove(it.key)
261+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
262+
}
263+
it is IncrementalMap.Update -> {
264+
var new = HashMap(cache!!.after)
265+
assertTrue("at \"$tag\", processing op $it: expected key to exist", new.contains(it.key))
266+
new[it.key] = it.after[it.key]
267+
assertEquals("at \"$tag\", expected match while processing op $it (old=${cache!!.after})", it.after, new)
268+
}
269+
else -> throw IllegalArgumentException("unknown command?")
270+
}
271+
emit(it)
272+
cache = it
66273
}
67274
}
68275
}

0 commit comments

Comments
 (0)