From 8d9f6736a2c07192124dbebb3444c06a977aa0a9 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 30 Jan 2020 18:33:19 +0300 Subject: [PATCH 1/6] Implement ObservableValue.asFlow() --- .../api/kotlinx-coroutines-javafx.api | 4 + .../src/JavaFxConvert.kt | 38 ++++++++ ...{JavaFxTest.kt => JavaFxDispatcherTest.kt} | 2 +- .../test/JavaFxObservableAsFlowTest.kt | 86 +++++++++++++++++++ .../test/JavaFxStressTest.kt | 39 +++++++++ 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt rename ui/kotlinx-coroutines-javafx/test/{JavaFxTest.kt => JavaFxDispatcherTest.kt} (97%) create mode 100644 ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt create mode 100644 ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt diff --git a/ui/kotlinx-coroutines-javafx/api/kotlinx-coroutines-javafx.api b/ui/kotlinx-coroutines-javafx/api/kotlinx-coroutines-javafx.api index 24c5b70b9d..620e904612 100644 --- a/ui/kotlinx-coroutines-javafx/api/kotlinx-coroutines-javafx.api +++ b/ui/kotlinx-coroutines-javafx/api/kotlinx-coroutines-javafx.api @@ -1,3 +1,7 @@ +public final class kotlinx/coroutines/javafx/JavaFxConvertKt { + public static final fun asFlow (Ljavafx/beans/value/ObservableValue;)Lkotlinx/coroutines/flow/Flow; +} + public abstract class kotlinx/coroutines/javafx/JavaFxDispatcher : kotlinx/coroutines/MainCoroutineDispatcher, kotlinx/coroutines/Delay { public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt new file mode 100644 index 0000000000..10c7e7be0d --- /dev/null +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt @@ -0,0 +1,38 @@ +package kotlinx.coroutines.javafx + +import javafx.beans.value.ChangeListener +import javafx.beans.value.ObservableValue +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn + +/** + * Creates an instance of a cold [Flow] that subscribes to the given [ObservableValue] and produces + * its values as they change. + * + * The resulting flow is conflated, meaning that if several values arrive in a quick succession, only + * the last one will be produced. + * + * It produces at least one value. + * + * Since this implementation uses [ObservableValue.addListener], even if this [ObservableValue] + * supports lazy evaluation, eager computation will be enforced while the flow is being collected. + */ +@ExperimentalCoroutinesApi +public fun ObservableValue.asFlow(): Flow = callbackFlow { + val listener = ChangeListener { _, _, newValue -> + try { + offer(newValue) + } catch (e: CancellationException) { + // In case the event fires after the channel is closed + } + } + addListener(listener) + send(value) + awaitClose { + removeListener(listener) + } +}.flowOn(Dispatchers.JavaFx).conflate() \ No newline at end of file diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt similarity index 97% rename from ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt rename to ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt index e6a1ddb414..724be6d77b 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt @@ -8,7 +8,7 @@ import javafx.application.* import kotlinx.coroutines.* import org.junit.* -class JavaFxTest : TestBase() { +class JavaFxDispatcherTest : TestBase() { @Before fun setup() { ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher") diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt new file mode 100644 index 0000000000..a39dedb551 --- /dev/null +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt @@ -0,0 +1,86 @@ +package kotlinx.coroutines.javafx + +import javafx.beans.property.SimpleIntegerProperty +import kotlinx.coroutines.TestBase +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import org.junit.Before +import org.junit.Test +import kotlin.test.* + + +class JavaFxObservableAsFlowTest : TestBase() { + + @Before + fun setup() { + ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher") + } + + @Test + fun testFlowOrder() = runTest { + if (!initPlatform()) { + println("Skipping JavaFxTest in headless environment") + return@runTest // ignore test in headless environments + } + + val integerProperty = SimpleIntegerProperty(0) + val n = 10000 * stressTestMultiplier + val flow = integerProperty.asFlow().takeWhile { j -> j != n } + newSingleThreadContext("setter").use { pool -> + launch(pool) { + for (i in 1..n) { + launch(Dispatchers.JavaFx) { + integerProperty.set(i) + } + } + } + var i = -1 + flow.collect { j -> + assertTrue(i < (j as Int), "Elements are neither repeated nor shuffled") + i = j + } + } + } + + @Test + fun testConflation() = runTest { + if (!initPlatform()) { + println("Skipping JavaFxTest in headless environment") + return@runTest // ignore test in headless environments + } + + withContext(Dispatchers.JavaFx) { + val END_MARKER = -1 + val integerProperty = SimpleIntegerProperty(0) + val flow = integerProperty.asFlow().takeWhile { j -> j != END_MARKER } + launch { + yield() // to subscribe to [integerProperty] + yield() // send 0 + integerProperty.set(1) + expect(3) + yield() // send 1 + expect(5) + integerProperty.set(2) + for (i in (-100..-2)) { + integerProperty.set(i) // should be skipped due to conflation + } + integerProperty.set(3) + expect(6) + yield() // send 2 and 3 + integerProperty.set(-1) + } + expect(1) + flow.collect { i -> + when (i) { + 0 -> expect(2) + 1 -> expect(4) + 2 -> expect(7) + 3 -> expect(8) + else -> fail("i is $i") + } + } + finish(9) + } + } + +} diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt new file mode 100644 index 0000000000..a572c0434c --- /dev/null +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt @@ -0,0 +1,39 @@ +package kotlinx.coroutines.javafx + +import javafx.beans.property.SimpleIntegerProperty +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.first +import org.junit.Before +import org.junit.Test + +class JavaFxStressTest : TestBase() { + + @Before + fun setup() { + ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher") + } + + @Test + fun cancellationRaceStressTest() = runTest { + if (!initPlatform()) { + println("Skipping JavaFxTest in headless environment") + return@runTest // ignore test in headless environments + } + + val integerProperty = SimpleIntegerProperty(0) + val flow = integerProperty.asFlow() + var i = 1 + val n = 1000 * stressTestMultiplier + newSingleThreadContext("collector").use { pool -> + repeat (n) { + launch(pool) { + flow.first() + } + withContext(Dispatchers.JavaFx) { + integerProperty.set(i) + } + i += 1 + } + } + } +} \ No newline at end of file From 6e5c79da667e0344117003fd5e74001c040246fe Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 13 Feb 2020 16:51:48 +0300 Subject: [PATCH 2/6] Add an integration test for ObservableValue.asFlow --- .../test/examples/FxAsFlow.kt | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt diff --git a/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt new file mode 100644 index 0000000000..bb0463bd79 --- /dev/null +++ b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt @@ -0,0 +1,101 @@ +package examples + +import javafx.application.Application +import javafx.scene.Scene +import javafx.scene.control.* +import javafx.scene.layout.GridPane +import javafx.stage.Stage +import javafx.beans.property.SimpleStringProperty +import javafx.event.EventHandler +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.javafx.* +import kotlin.coroutines.CoroutineContext + +fun main(args: Array) { + Application.launch(FxAsFlowApp::class.java, *args) +} + +/** + * Adapted from + * https://github.com/ReactiveX/RxJavaFX/blob/a78ca7d15f7d82d201df8fafb6eba732ec17e327/src/test/java/io/reactivex/rxjavafx/RxJavaFXTest.java + */ +class FxAsFlowApp: Application(), CoroutineScope { + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = JavaFx + job + + private val incrementBttn = Button("Increment") + private val incrementLabel = Label("") + private val textInput = TextField() + private val flippedTextLabel = Label() + private val spinner = Spinner() + private val spinnerChangesLabel = Label() + + public override fun start( primaryStage: Stage) { + val gridPane = GridPane() + gridPane.apply { + hgap = 10.0 + vgap = 10.0 + add(incrementBttn, 0, 0) + add(incrementLabel, 1, 0) + add(textInput, 0, 1) + add(flippedTextLabel, 1, 1) + add(spinner, 0, 2) + add(spinnerChangesLabel, 1, 2) + } + val scene = Scene(gridPane) + primaryStage.apply { + width = 275.0 + setScene(scene) + show() + } + } + + public override fun stop() { + super.stop() + job.cancel() + job = Job() + } + + init { + // Initializing the "Increment" button + val stringProperty = SimpleStringProperty("") + var i = 0 + incrementBttn.onAction = EventHandler { + i += 1 + stringProperty.set(i.toString()) + } + launch { + stringProperty.asFlow().collect { + if (it != null) { + stringProperty.set(it) + } + } + } + incrementLabel.textProperty().bind(stringProperty) + // Initializing the reversed text field + val stringProperty2 = SimpleStringProperty("") + launch { + textInput.textProperty().asFlow().collect { + if (it != null) { + stringProperty2.set(it.reversed()) + } + } + } + flippedTextLabel.textProperty().bind(stringProperty2) + // Initializing the spinner + spinner.valueFactory = SpinnerValueFactory.IntegerSpinnerValueFactory(0, 100) + spinner.isEditable = true + val stringProperty3 = SimpleStringProperty("") + launch { + spinner.valueProperty().asFlow().collect { + if (it != null) { + stringProperty3.set("NEW: $it") + } + } + } + spinnerChangesLabel.textProperty().bind(stringProperty3) + } +} From 0673361cabf29e2e9c5c774d57f00315c71fe2ae Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 21 Feb 2020 17:54:20 +0300 Subject: [PATCH 3/6] Fixes --- .../src/JavaFxConvert.kt | 13 ++++++++++++- .../test/JavaFxObservableAsFlowTest.kt | 2 +- .../test/JavaFxStressTest.kt | 2 +- .../test/examples/FxAsFlow.kt | 18 +++++++++--------- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt index 10c7e7be0d..9725c03f32 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + package kotlinx.coroutines.javafx import javafx.beans.value.ChangeListener @@ -20,9 +24,16 @@ import kotlinx.coroutines.flow.flowOn * * Since this implementation uses [ObservableValue.addListener], even if this [ObservableValue] * supports lazy evaluation, eager computation will be enforced while the flow is being collected. + * + * All the calls to JavaFX API are performed in the JavaFX application thread. + * + * ### Operator fusion + * + * Adjacent applications of [flowOn], [buffer], [conflate], and [produceIn] to the result of `asFlow` are fused. + * [conflate] has no effect, as this flow is already conflated; one can use [buffer] to change that instead. */ @ExperimentalCoroutinesApi -public fun ObservableValue.asFlow(): Flow = callbackFlow { +public fun ObservableValue.asFlow(): Flow = callbackFlow { val listener = ChangeListener { _, _, newValue -> try { offer(newValue) diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt index a39dedb551..6964050102 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt @@ -24,7 +24,7 @@ class JavaFxObservableAsFlowTest : TestBase() { } val integerProperty = SimpleIntegerProperty(0) - val n = 10000 * stressTestMultiplier + val n = 1000 val flow = integerProperty.asFlow().takeWhile { j -> j != n } newSingleThreadContext("setter").use { pool -> launch(pool) { diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt index a572c0434c..fd7436214a 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt @@ -14,7 +14,7 @@ class JavaFxStressTest : TestBase() { } @Test - fun cancellationRaceStressTest() = runTest { + fun testCancellationRace() = runTest { if (!initPlatform()) { println("Skipping JavaFxTest in headless environment") return@runTest // ignore test in headless environments diff --git a/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt index bb0463bd79..3a3745c743 100644 --- a/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt +++ b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt @@ -26,12 +26,12 @@ class FxAsFlowApp: Application(), CoroutineScope { override val coroutineContext: CoroutineContext get() = JavaFx + job - private val incrementBttn = Button("Increment") - private val incrementLabel = Label("") - private val textInput = TextField() - private val flippedTextLabel = Label() - private val spinner = Spinner() - private val spinnerChangesLabel = Label() + private val incrementBttn = Button("Increment") + private val incrementLabel = Label("") + private val textInput = TextField() + private val flippedTextLabel = Label() + private val spinner = Spinner() + private val spinnerChangesLabel = Label() public override fun start( primaryStage: Stage) { val gridPane = GridPane() @@ -61,7 +61,7 @@ class FxAsFlowApp: Application(), CoroutineScope { init { // Initializing the "Increment" button - val stringProperty = SimpleStringProperty("") + val stringProperty = SimpleStringProperty() var i = 0 incrementBttn.onAction = EventHandler { i += 1 @@ -76,7 +76,7 @@ class FxAsFlowApp: Application(), CoroutineScope { } incrementLabel.textProperty().bind(stringProperty) // Initializing the reversed text field - val stringProperty2 = SimpleStringProperty("") + val stringProperty2 = SimpleStringProperty() launch { textInput.textProperty().asFlow().collect { if (it != null) { @@ -88,7 +88,7 @@ class FxAsFlowApp: Application(), CoroutineScope { // Initializing the spinner spinner.valueFactory = SpinnerValueFactory.IntegerSpinnerValueFactory(0, 100) spinner.isEditable = true - val stringProperty3 = SimpleStringProperty("") + val stringProperty3 = SimpleStringProperty() launch { spinner.valueProperty().asFlow().collect { if (it != null) { From 3642a23f3e3ae9fd5ad5b342c273429ae91f3796 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 21 Feb 2020 18:04:36 +0300 Subject: [PATCH 4/6] Fix --- ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt | 5 +---- ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt | 2 +- ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt index 9725c03f32..f5f595e169 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt @@ -8,10 +8,7 @@ import javafx.beans.value.ChangeListener import javafx.beans.value.ObservableValue import kotlinx.coroutines.* import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.* /** * Creates an instance of a cold [Flow] that subscribes to the given [ObservableValue] and produces diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt index 8f5be31dd4..ed74ad6a56 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt @@ -116,7 +116,7 @@ private class PulseTimer : AnimationTimer() { } } -/** @return [true] if initialized successfully, and [false] if no display is detected */ +/** @return true if initialized successfully, and false if no display is detected */ internal fun initPlatform(): Boolean = PlatformInitializer.success // Lazily try to initialize JavaFx platform just once diff --git a/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt index 3a3745c743..00003f7860 100644 --- a/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt +++ b/ui/kotlinx-coroutines-javafx/test/examples/FxAsFlow.kt @@ -26,7 +26,7 @@ class FxAsFlowApp: Application(), CoroutineScope { override val coroutineContext: CoroutineContext get() = JavaFx + job - private val incrementBttn = Button("Increment") + private val incrementButton = Button("Increment") private val incrementLabel = Label("") private val textInput = TextField() private val flippedTextLabel = Label() @@ -38,7 +38,7 @@ class FxAsFlowApp: Application(), CoroutineScope { gridPane.apply { hgap = 10.0 vgap = 10.0 - add(incrementBttn, 0, 0) + add(incrementButton, 0, 0) add(incrementLabel, 1, 0) add(textInput, 0, 1) add(flippedTextLabel, 1, 1) @@ -63,7 +63,7 @@ class FxAsFlowApp: Application(), CoroutineScope { // Initializing the "Increment" button val stringProperty = SimpleStringProperty() var i = 0 - incrementBttn.onAction = EventHandler { + incrementButton.onAction = EventHandler { i += 1 stringProperty.set(i.toString()) } From 43e5f9766ea58ac7490bfe17b2fe6c9e362c71e5 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 4 Mar 2020 14:08:00 +0300 Subject: [PATCH 5/6] Fix wording in documentation --- ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt index f5f595e169..e350fc6bf6 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt @@ -11,18 +11,12 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.* /** - * Creates an instance of a cold [Flow] that subscribes to the given [ObservableValue] and produces - * its values as they change. - * - * The resulting flow is conflated, meaning that if several values arrive in a quick succession, only - * the last one will be produced. - * - * It produces at least one value. - * + * Creates an instance of a cold [Flow] that subscribes to the given [ObservableValue] and emits + * its values as they change. The resulting flow is conflated, meaning that if several values arrive in quick + * succession, only the last one will be emitted. * Since this implementation uses [ObservableValue.addListener], even if this [ObservableValue] * supports lazy evaluation, eager computation will be enforced while the flow is being collected. - * - * All the calls to JavaFX API are performed in the JavaFX application thread. + * All the calls to JavaFX API are performed in [Dispatchers.JavaFx]. * * ### Operator fusion * From 3568105d8171c9b94c3f7845439f56591031d243 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 4 Mar 2020 16:41:26 +0300 Subject: [PATCH 6/6] Fix --- .../src/JavaFxConvert.kt | 3 ++- .../test/JavaFxStressTest.kt | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt index e350fc6bf6..903b60a2cf 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.* * Since this implementation uses [ObservableValue.addListener], even if this [ObservableValue] * supports lazy evaluation, eager computation will be enforced while the flow is being collected. * All the calls to JavaFX API are performed in [Dispatchers.JavaFx]. + * This flow emits at least the initial value. * * ### Operator fusion * @@ -37,4 +38,4 @@ public fun ObservableValue.asFlow(): Flow = callbackFlow { awaitClose { removeListener(listener) } -}.flowOn(Dispatchers.JavaFx).conflate() \ No newline at end of file +}.flowOn(Dispatchers.JavaFx).conflate() diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt index fd7436214a..5338835d84 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxStressTest.kt @@ -3,8 +3,7 @@ package kotlinx.coroutines.javafx import javafx.beans.property.SimpleIntegerProperty import kotlinx.coroutines.* import kotlinx.coroutines.flow.first -import org.junit.Before -import org.junit.Test +import org.junit.* class JavaFxStressTest : TestBase() { @@ -13,6 +12,9 @@ class JavaFxStressTest : TestBase() { ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher") } + @get:Rule + val pool = ExecutorRule(1) + @Test fun testCancellationRace() = runTest { if (!initPlatform()) { @@ -24,16 +26,14 @@ class JavaFxStressTest : TestBase() { val flow = integerProperty.asFlow() var i = 1 val n = 1000 * stressTestMultiplier - newSingleThreadContext("collector").use { pool -> - repeat (n) { - launch(pool) { - flow.first() - } - withContext(Dispatchers.JavaFx) { - integerProperty.set(i) - } - i += 1 + repeat (n) { + launch(pool) { + flow.first() + } + withContext(Dispatchers.JavaFx) { + integerProperty.set(i) } + i += 1 } } } \ No newline at end of file