5
5
package kotlinx.coroutines.jdk9
6
6
7
7
import kotlinx.coroutines.*
8
+ import kotlinx.coroutines.channels.*
8
9
import org.junit.Test
9
10
import java.util.concurrent.Flow as JFlow
10
11
import kotlin.test.*
@@ -121,44 +122,110 @@ class PublishTest : TestBase() {
121
122
finish(7 )
122
123
}
123
124
125
+ /* * Tests that, as soon as `ProducerScope.close` is called, `isClosedForSend` starts returning `true`. */
124
126
@Test
125
- fun testOnNextError () = runTest {
127
+ fun testChannelClosing () = runTest {
126
128
expect(1 )
127
- val publisher = flowPublish(currentDispatcher()) {
129
+ val publisher = flowPublish<Int >(Dispatchers .Unconfined ) {
130
+ expect(3 )
131
+ close()
132
+ assert (isClosedForSend)
128
133
expect(4 )
129
- try {
130
- send(" OK" )
131
- } catch (e: Throwable ) {
132
- expect(6 )
133
- assert (e is TestException )
134
- }
135
134
}
136
- expect(2 )
135
+ try {
136
+ expect(2 )
137
+ publisher.awaitFirstOrNull()
138
+ } catch (e: CancellationException ) {
139
+ expect(5 )
140
+ }
141
+ finish(6 )
142
+ }
143
+
144
+ @Test
145
+ fun testOnNextError () = runTest {
137
146
val latch = CompletableDeferred <Unit >()
138
- publisher.subscribe(object : JFlow .Subscriber <String > {
139
- override fun onComplete () {
140
- expectUnreached()
147
+ expect(1 )
148
+ assertCallsExceptionHandlerWith<TestException > { exceptionHandler ->
149
+ val publisher = flowPublish(currentDispatcher() + exceptionHandler) {
150
+ expect(4 )
151
+ try {
152
+ send(" OK" )
153
+ } catch (e: Throwable ) {
154
+ expect(6 )
155
+ assert (e is TestException )
156
+ assert (isClosedForSend)
157
+ latch.complete(Unit )
158
+ }
141
159
}
160
+ expect(2 )
161
+ publisher.subscribe(object : JFlow .Subscriber <String > {
162
+ override fun onComplete () {
163
+ expectUnreached()
164
+ }
142
165
143
- override fun onSubscribe (s : JFlow .Subscription ) {
144
- expect(3 )
145
- s.request(1 )
146
- }
166
+ override fun onSubscribe (s : JFlow .Subscription ) {
167
+ expect(3 )
168
+ s.request(1 )
169
+ }
147
170
148
- override fun onNext (t : String ) {
149
- expect(5 )
150
- assertEquals(" OK" , t)
151
- throw TestException ()
152
- }
171
+ override fun onNext (t : String ) {
172
+ expect(5 )
173
+ assertEquals(" OK" , t)
174
+ throw TestException ()
175
+ }
153
176
154
- override fun onError (t : Throwable ) {
155
- expect(7 )
156
- assert (t is TestException )
157
- latch.complete(Unit )
177
+ override fun onError (t : Throwable ) {
178
+ expectUnreached()
179
+ }
180
+ })
181
+ latch.await()
182
+ }
183
+ finish(7 )
184
+ }
185
+
186
+ /* * Tests the behavior when a call to `onNext` fails after the channel is already closed. */
187
+ @Test
188
+ fun testOnNextErrorAfterCancellation () = runTest {
189
+ assertCallsExceptionHandlerWith<TestException > { handler ->
190
+ var producerScope: ProducerScope <Int >? = null
191
+ CompletableDeferred <Unit >()
192
+ expect(1 )
193
+ var job: Job ? = null
194
+ val publisher = flowPublish<Int >(handler + Dispatchers .Unconfined ) {
195
+ producerScope = this
196
+ expect(4 )
197
+ job = launch {
198
+ delay(Long .MAX_VALUE )
199
+ }
158
200
}
159
- })
160
- latch.await()
161
- finish(8 )
201
+ expect(2 )
202
+ publisher.subscribe(object : JFlow .Subscriber <Int > {
203
+ override fun onSubscribe (s : JFlow .Subscription ) {
204
+ expect(3 )
205
+ s.request(Long .MAX_VALUE )
206
+ }
207
+ override fun onNext (t : Int ) {
208
+ expect(6 )
209
+ assertEquals(1 , t)
210
+ job!! .cancel()
211
+ throw TestException ()
212
+ }
213
+ override fun onError (t : Throwable ? ) {
214
+ /* Correct changes to the implementation could lead to us entering or not entering this method, but
215
+ it only matters that if we do, it is the "correct" exception that was validly used to cancel the
216
+ coroutine that gets passed here and not `TestException`. */
217
+ assertTrue(t is CancellationException )
218
+ }
219
+ override fun onComplete () { expectUnreached() }
220
+ })
221
+ expect(5 )
222
+ val result: ChannelResult <Unit > = producerScope!! .trySend(1 )
223
+ val e = result.exceptionOrNull()!!
224
+ assertTrue(e is CancellationException , " The actual error: $e " )
225
+ assertTrue(producerScope!! .isClosedForSend)
226
+ assertTrue(result.isFailure)
227
+ }
228
+ finish(7 )
162
229
}
163
230
164
231
@Test
@@ -182,4 +249,39 @@ class PublishTest : TestBase() {
182
249
fun testIllegalArgumentException () {
183
250
assertFailsWith<IllegalArgumentException > { flowPublish<Int >(Job ()) { } }
184
251
}
252
+
253
+ /* * Tests that `trySend` doesn't throw in `flowPublish`. */
254
+ @Test
255
+ fun testTrySendNotThrowing () = runTest {
256
+ var producerScope: ProducerScope <Int >? = null
257
+ expect(1 )
258
+ val publisher = flowPublish<Int >(Dispatchers .Unconfined ) {
259
+ producerScope = this
260
+ expect(3 )
261
+ delay(Long .MAX_VALUE )
262
+ }
263
+ val job = launch(start = CoroutineStart .UNDISPATCHED ) {
264
+ expect(2 )
265
+ publisher.awaitFirstOrNull()
266
+ expectUnreached()
267
+ }
268
+ job.cancel()
269
+ expect(4 )
270
+ val result = producerScope!! .trySend(1 )
271
+ assertTrue(result.isFailure)
272
+ finish(5 )
273
+ }
274
+
275
+ /* * Tests that all methods on `flowPublish` fail without closing the channel when attempting to emit `null`. */
276
+ @Test
277
+ fun testEmittingNull () = runTest {
278
+ val publisher = flowPublish {
279
+ assertFailsWith<NullPointerException > { send(null ) }
280
+ assertFailsWith<NullPointerException > { trySend(null ) }
281
+ @Suppress(" DEPRECATION" )
282
+ assertFailsWith<NullPointerException > { offer(null ) }
283
+ send(" OK" )
284
+ }
285
+ assertEquals(" OK" , publisher.awaitFirstOrNull())
286
+ }
185
287
}
0 commit comments