Skip to content

Commit 99cbb7f

Browse files
committed
fix: Coroutine edge cases for ByteArray/nested collections
Fix suspend and Flow decode paths for List<ByteArray> and List<List<T>> elements. Add NestedColl reader/wrap bridges. 35 new coroutine battle tests (1014 total) covering all features under concurrent pressure.
1 parent 5dfb732 commit 99cbb7f

5 files changed

Lines changed: 552 additions & 0 deletions

File tree

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
package com.example.calculator
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertContentEquals
6+
import kotlin.test.assertTrue
7+
import kotlin.test.assertNotNull
8+
import kotlinx.coroutines.runBlocking
9+
import kotlinx.coroutines.async
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.awaitAll
12+
import kotlinx.coroutines.delay
13+
import kotlinx.coroutines.flow.toList
14+
import kotlinx.coroutines.flow.first
15+
16+
class CoroutineCrossFeatureTest {
17+
18+
// ══════════════════════════════════════════════════════════════════════════
19+
// CROSS-FEATURE COROUTINE BATTLE TESTS
20+
// Every feature tested under concurrent coroutine pressure
21+
// ══════════════════════════════════════════════════════════════════════════
22+
23+
// ── ByteArray everywhere in coroutines ──────────────────────────────────
24+
25+
@Test
26+
fun `coroutine - ByteArray callback concurrent`() = runBlocking {
27+
Calculator(5).use { calc ->
28+
val results = (1..20).map {
29+
async(Dispatchers.Default) {
30+
val transformed = calc.transformBytes(byteArrayOf(1, 2, 3)) { it.reversedArray() }
31+
transformed
32+
}
33+
}.awaitAll()
34+
results.forEach { assertContentEquals(byteArrayOf(3, 2, 1), it) }
35+
}
36+
}
37+
38+
@Test
39+
fun `coroutine - ByteArray DC field concurrent`() = runBlocking {
40+
Calculator(4).use { calc ->
41+
val results = (1..20).map {
42+
async(Dispatchers.Default) { calc.getBinaryPayload() }
43+
}.awaitAll()
44+
results.forEach {
45+
assertEquals("payload_4", it.name)
46+
assertEquals(4, it.data.size)
47+
}
48+
}
49+
}
50+
51+
@Test
52+
fun `coroutine - ByteArray DC field roundtrip concurrent`() = runBlocking {
53+
val results = (1..10).map { i ->
54+
async(Dispatchers.Default) {
55+
Calculator(i).use { calc ->
56+
val bp = calc.getBinaryPayload()
57+
calc.applyBinaryPayload(bp)
58+
}
59+
}
60+
}.awaitAll()
61+
results.forEachIndexed { idx, r -> assertEquals(idx + 1, r) }
62+
}
63+
64+
@Test
65+
fun `coroutine - List ByteArray concurrent`() = runBlocking {
66+
Calculator(2).use { calc ->
67+
val results = (1..10).map {
68+
async(Dispatchers.Default) { calc.getByteChunks() }
69+
}.awaitAll()
70+
results.forEach { assertEquals(3, it.size) }
71+
}
72+
}
73+
74+
// ── List<DataClass> param in coroutines ─────────────────────────────────
75+
76+
@Test
77+
fun `coroutine - List DC param concurrent`() = runBlocking {
78+
Calculator(0).use { calc ->
79+
val results = (1..20).map { i ->
80+
async(Dispatchers.Default) {
81+
calc.sumPoints(listOf(Point(i, i), Point(i * 2, 0)))
82+
}
83+
}.awaitAll()
84+
assertEquals(20, results.size)
85+
results.forEach { assertTrue(it > 0) }
86+
}
87+
}
88+
89+
@Test
90+
fun `coroutine - List DC param separate instances`() = runBlocking {
91+
val results = (1..20).map { i ->
92+
async(Dispatchers.Default) {
93+
Calculator(0).use { calc ->
94+
calc.describeNamedValues(listOf(NamedValue("test_$i", i)))
95+
}
96+
}
97+
}.awaitAll()
98+
results.forEachIndexed { idx, r -> assertTrue(r.contains("test_${idx + 1}")) }
99+
}
100+
101+
// ── Collection properties in coroutines ─────────────────────────────────
102+
103+
@Test
104+
fun `coroutine - collection property read concurrent`() = runBlocking {
105+
Calculator(7).use { calc ->
106+
val results = (1..20).map {
107+
async(Dispatchers.Default) { calc.recentScores }
108+
}.awaitAll()
109+
results.forEach { assertEquals(listOf(7, 14, 21), it) }
110+
}
111+
}
112+
113+
@Test
114+
fun `coroutine - collection property write-read concurrent`() = runBlocking {
115+
Calculator(0).use { calc ->
116+
val results = (1..10).map { i ->
117+
async(Dispatchers.Default) {
118+
calc.tags = listOf("tag_$i")
119+
delay(1)
120+
calc.tags
121+
}
122+
}.awaitAll()
123+
assertEquals(10, results.size)
124+
}
125+
}
126+
127+
@Test
128+
fun `coroutine - map property concurrent`() = runBlocking {
129+
Calculator(42).use { calc ->
130+
val results = (1..20).map {
131+
async(Dispatchers.Default) { calc.info }
132+
}.awaitAll()
133+
results.forEach { assertEquals(42, it["current"]) }
134+
}
135+
}
136+
137+
// ── Nullable callbacks in coroutines ────────────────────────────────────
138+
139+
@Test
140+
fun `coroutine - nullable callback mixed null-nonnull`() = runBlocking {
141+
Calculator(10).use { calc ->
142+
val results = (1..20).map { i ->
143+
async(Dispatchers.Default) {
144+
if (i % 2 == 0) calc.formatOrNull { "v=$it" }
145+
else calc.formatOrNull(null)
146+
}
147+
}.awaitAll()
148+
results.forEachIndexed { idx, r ->
149+
if ((idx + 1) % 2 == 0) assertEquals("v=10", r)
150+
else assertEquals("null", r)
151+
}
152+
}
153+
}
154+
155+
// ── Lambda return type in coroutines ────────────────────────────────────
156+
157+
@Test
158+
fun `coroutine - lambda return concurrent create`() = runBlocking {
159+
Calculator(0).use { calc ->
160+
val adders = (1..20).map { i ->
161+
async(Dispatchers.Default) { calc.getAdder(i) }
162+
}.awaitAll()
163+
adders.forEachIndexed { idx, adder -> assertEquals(100 + idx + 1, adder(100)) }
164+
}
165+
}
166+
167+
@Test
168+
fun `coroutine - lambda return concurrent invoke`() = runBlocking {
169+
Calculator(0).use { calc ->
170+
val adder = calc.getAdder(5)
171+
val results = (1..20).map { i ->
172+
async(Dispatchers.Default) { adder(i) }
173+
}.awaitAll()
174+
results.forEachIndexed { idx, r -> assertEquals(idx + 1 + 5, r) }
175+
}
176+
}
177+
178+
// ── Flow<Collection> in coroutines ──────────────────────────────────────
179+
180+
@Test
181+
fun `coroutine - Flow List concurrent collectors`() = runBlocking {
182+
Calculator(0).use { calc ->
183+
val results = (1..5).map {
184+
async(Dispatchers.Default) { calc.scoresFlow(3).toList() }
185+
}.awaitAll()
186+
results.forEach { assertEquals(3, it.size) }
187+
}
188+
}
189+
190+
@Test
191+
fun `coroutine - Flow Map concurrent`() = runBlocking {
192+
Calculator(5).use { calc ->
193+
val results = (1..5).map {
194+
async(Dispatchers.Default) { calc.metadataFlow(2).toList() }
195+
}.awaitAll()
196+
results.forEach {
197+
assertEquals(2, it.size)
198+
assertEquals(5, it[0]["value"])
199+
}
200+
}
201+
}
202+
203+
@Test
204+
fun `coroutine - Flow ByteArray concurrent`() = runBlocking {
205+
Calculator(0).use { calc ->
206+
val results = (1..5).map {
207+
async(Dispatchers.Default) { calc.byteChunks(2, 4).toList() }
208+
}.awaitAll()
209+
results.forEach {
210+
assertEquals(2, it.size)
211+
assertEquals(4, it[0].size)
212+
}
213+
}
214+
}
215+
216+
// ── Nested collections in coroutines ────────────────────────────────────
217+
218+
@Test
219+
fun `coroutine - nested List return concurrent`() = runBlocking {
220+
Calculator(3).use { calc ->
221+
val results = (1..20).map {
222+
async(Dispatchers.Default) { calc.getMatrix() }
223+
}.awaitAll()
224+
results.forEach {
225+
assertEquals(listOf(3, 4), it[0])
226+
assertEquals(listOf(6, 7), it[1])
227+
}
228+
}
229+
}
230+
231+
@Test
232+
fun `coroutine - nested List param concurrent`() = runBlocking {
233+
val results = (1..20).map { i ->
234+
async(Dispatchers.Default) {
235+
Calculator(0).use { calc ->
236+
calc.sumMatrix(listOf(listOf(i, i), listOf(i)))
237+
}
238+
}
239+
}.awaitAll()
240+
results.forEachIndexed { idx, sum -> assertEquals((idx + 1) * 3, sum) }
241+
}
242+
243+
// ── Suspend with ByteArray/nested collections in coroutines ─────────────
244+
245+
@Test
246+
fun `coroutine - suspend List ByteArray concurrent`() = runBlocking {
247+
Calculator(2).use { calc ->
248+
val results = (1..10).map {
249+
async(Dispatchers.Default) { calc.delayedGetByteChunks() }
250+
}.awaitAll()
251+
results.forEach {
252+
assertEquals(2, it.size)
253+
assertContentEquals(byteArrayOf(1, 2, 3), it[0])
254+
}
255+
}
256+
}
257+
258+
@Test
259+
fun `coroutine - suspend nested List concurrent`() = runBlocking {
260+
Calculator(5).use { calc ->
261+
val results = (1..10).map {
262+
async(Dispatchers.Default) { calc.delayedGetMatrix() }
263+
}.awaitAll()
264+
results.forEach {
265+
assertEquals(listOf(5, 6), it[0])
266+
assertEquals(listOf(10), it[1])
267+
}
268+
}
269+
}
270+
271+
// ── DC with collection fields in coroutines ─────────────────────────────
272+
273+
@Test
274+
fun `coroutine - DC with List field concurrent`() = runBlocking {
275+
Calculator(5).use { calc ->
276+
val results = (1..20).map {
277+
async(Dispatchers.Default) { calc.getTaggedList() }
278+
}.awaitAll()
279+
results.forEach { assertEquals(listOf(5, 10, 15), it.scores) }
280+
}
281+
}
282+
283+
@Test
284+
fun `coroutine - DC with Map field concurrent`() = runBlocking {
285+
Calculator(10).use { calc ->
286+
val results = (1..20).map {
287+
async(Dispatchers.Default) { calc.getMetadataHolder() }
288+
}.awaitAll()
289+
results.forEach { assertEquals(10, it.metadata["current"]) }
290+
}
291+
}
292+
293+
@Test
294+
fun `coroutine - DC with collection field roundtrip concurrent`() = runBlocking {
295+
val results = (1..10).map { i ->
296+
async(Dispatchers.Default) {
297+
Calculator(i).use { calc ->
298+
val tl = calc.getTaggedList()
299+
calc.applyTaggedList(tl)
300+
}
301+
}
302+
}.awaitAll()
303+
results.forEachIndexed { idx, sum ->
304+
val v = idx + 1
305+
assertEquals(v + v * 2 + v * 3, sum)
306+
}
307+
}
308+
309+
// ── Suspend DC return in coroutines ──────────────────────────────────────
310+
311+
@Test
312+
fun `coroutine - suspend DC return concurrent`() = runBlocking {
313+
Calculator(5).use { calc ->
314+
val results = (1..10).map {
315+
async(Dispatchers.Default) { calc.delayedGetPoint() }
316+
}.awaitAll()
317+
results.forEach { assertEquals(Point(5, 10), it) }
318+
}
319+
}
320+
321+
@Test
322+
fun `coroutine - suspend List return concurrent`() = runBlocking {
323+
Calculator(5).use { calc ->
324+
val results = (1..10).map {
325+
async(Dispatchers.Default) { calc.delayedGetScores() }
326+
}.awaitAll()
327+
results.forEach { assertEquals(listOf(5, 10, 15), it) }
328+
}
329+
}
330+
331+
@Test
332+
fun `coroutine - suspend Map return concurrent`() = runBlocking {
333+
Calculator(42).use { calc ->
334+
val results = (1..10).map {
335+
async(Dispatchers.Default) { calc.delayedGetMetadata() }
336+
}.awaitAll()
337+
results.forEach { assertEquals(42, it["current"]) }
338+
}
339+
}
340+
341+
// ── Full cross-feature stress: all features in one coroutine ─────────────
342+
343+
@Test
344+
fun `coroutine - all features interleaved`() = runBlocking {
345+
Calculator(5).use { calc ->
346+
val results = (1..10).map { i ->
347+
async(Dispatchers.Default) {
348+
// Suspend DC
349+
val p = calc.delayedGetPoint()
350+
// List<DC> param
351+
calc.sumPoints(listOf(p, Point(i, i)))
352+
// Collection property
353+
val scores = calc.recentScores
354+
// DC with collection field
355+
val tl = calc.getTaggedList()
356+
// Nullable callback
357+
val fmt = if (i % 2 == 0) calc.formatOrNull { "v=$it" } else calc.formatOrNull(null)
358+
// Lambda return
359+
val adder = calc.getAdder(i)
360+
// Nested collection
361+
val matrix = calc.getMatrix()
362+
// Flow first
363+
val firstScores = calc.scoresFlow(2).first()
364+
// ByteArray callback
365+
var ba: ByteArray? = null
366+
calc.onBytesReady { ba = it }
367+
368+
listOf(p.x, scores.size, tl.scores.size, adder(0), matrix.size, firstScores.size, ba!!.size)
369+
}
370+
}.awaitAll()
371+
372+
assertEquals(10, results.size)
373+
results.forEach { vals ->
374+
assertEquals(5, vals[0]) // point.x
375+
assertEquals(3, vals[1]) // scores.size
376+
assertEquals(3, vals[2]) // taggedList.scores.size
377+
assertTrue(vals[3] > 0) // adder(0) > 0
378+
assertEquals(2, vals[4]) // matrix.size
379+
assertEquals(3, vals[5]) // firstScores.size
380+
assertTrue(vals[6] >= 0) // ba.size
381+
}
382+
}
383+
}
384+
}

0 commit comments

Comments
 (0)