From ddd916e92326f8f3dcc5a73140f773c7a26762ac Mon Sep 17 00:00:00 2001 From: Daniel Rees Date: Thu, 20 Jun 2019 09:30:24 -0400 Subject: [PATCH 1/3] Updated ManualDispatchQueue to run items scheduled during a tick --- .../org/phoenixframework/ChannelTest.kt | 2 +- .../queue/ManualDispatchQueue.kt | 75 ++++++++++ .../queue/ManualDispatchQueueTest.kt | 133 ++++++++++++++++++ .../queue/ManualDispatchWorkItem.kt | 33 +++++ .../utilities/TestUtilities.kt | 104 -------------- 5 files changed, 242 insertions(+), 105 deletions(-) create mode 100644 src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt create mode 100644 src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt create mode 100644 src/test/kotlin/org/phoenixframework/queue/ManualDispatchWorkItem.kt diff --git a/src/test/kotlin/org/phoenixframework/ChannelTest.kt b/src/test/kotlin/org/phoenixframework/ChannelTest.kt index 80dd2e8..30b999a 100644 --- a/src/test/kotlin/org/phoenixframework/ChannelTest.kt +++ b/src/test/kotlin/org/phoenixframework/ChannelTest.kt @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.stubbing.Answer -import org.phoenixframework.utilities.ManualDispatchQueue +import org.phoenixframework.queue.ManualDispatchQueue import org.phoenixframework.utilities.getBindings class ChannelTest { diff --git a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt new file mode 100644 index 0000000..2a79351 --- /dev/null +++ b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt @@ -0,0 +1,75 @@ +package org.phoenixframework.queue + +import org.phoenixframework.DispatchQueue +import org.phoenixframework.DispatchWorkItem +import java.util.concurrent.TimeUnit + +class ManualDispatchQueue : DispatchQueue { + + private var tickTime: Long = 0 + private val tickTimeUnit: TimeUnit = TimeUnit.MILLISECONDS + private var workItems: MutableList = mutableListOf() + + fun reset() { + this.tickTime = 0 + this.workItems = mutableListOf() + } + + fun tick(duration: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { + val durationInMs = tickTimeUnit.convert(duration, unit) + + // calculate what time to advance to + val advanceTo = tickTime + durationInMs + + // Filter all work items that are due to be fired and have not been + // cancelled. Return early if there are no items to fire + var pastDueWorkItems = workItems.filter { it.isPastDue(advanceTo) && !it.isCancelled } + + // Keep looping until there are no more work items that are passed the advance to time + while (pastDueWorkItems.isNotEmpty()) { + + // Perform all work items that are due + pastDueWorkItems.forEach { + tickTime = it.deadline + it.perform() + } + + // Remove all work items that are past due or canceled + workItems.removeAll { it.isPastDue(tickTime) || it.isCancelled } + pastDueWorkItems = workItems.filter { it.isPastDue(advanceTo) && !it.isCancelled } + } + + // Now that all work has been performed, advance the clock + this.tickTime = advanceTo + + } + + override fun queue(delay: Long, unit: TimeUnit, runnable: () -> Unit): DispatchWorkItem { + // Converts the given unit and delay to the unit used by this class + val delayInMs = tickTimeUnit.convert(delay, unit) + val deadline = tickTime + delayInMs + + val workItem = ManualDispatchWorkItem(runnable, deadline) + workItems.add(workItem) + + return workItem + } + + override fun queueAtFixedRate( + delay: Long, + period: Long, + unit: TimeUnit, + runnable: () -> Unit + ): DispatchWorkItem { + + val delayInMs = tickTimeUnit.convert(delay, unit) + val periodInMs = tickTimeUnit.convert(period, unit) + val deadline = tickTime + delayInMs + + val workItem = + ManualDispatchWorkItem(runnable, deadline, periodInMs) + workItems.add(workItem) + + return workItem + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt new file mode 100644 index 0000000..1cedc32 --- /dev/null +++ b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt @@ -0,0 +1,133 @@ +package org.phoenixframework.queue + +import com.google.common.truth.Truth.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit + +internal class ManualDispatchQueueTest { + + + private lateinit var queue: ManualDispatchQueue + + @BeforeEach + internal fun setUp() { + queue = ManualDispatchQueue() + } + + @AfterEach + internal fun tearDown() { + queue.reset() + } + + @Test + internal fun `triggers work that is passed due`() { + var task100Called = false + var task200Called = false + var task300Called = false + + queue.queue(100, TimeUnit.MILLISECONDS) { + task100Called = true + } + queue.queue(200, TimeUnit.MILLISECONDS) { + task200Called = true + } + queue.queue(300, TimeUnit.MILLISECONDS) { + task300Called = true + } + + queue.tick(100) + assertThat(task100Called).isTrue() + + queue.tick(100) + assertThat(task200Called).isTrue() + + queue.tick(50) + assertThat(task300Called).isFalse() + } + + @Test + internal fun `triggers all work that is passed due`() { + var task100Called = false + var task200Called = false + var task300Called = false + + queue.queue(100, TimeUnit.MILLISECONDS) { + task100Called = true + } + queue.queue(200, TimeUnit.MILLISECONDS) { + task200Called = true + } + queue.queue(300, TimeUnit.MILLISECONDS) { + task300Called = true + } + + queue.tick(250) + assertThat(task100Called).isTrue() + assertThat(task200Called).isTrue() + assertThat(task300Called).isFalse() + } + + @Test + internal fun `triggers work that is scheduled for a time that is after tick`() { + var task100Called = false + var task200Called = false + var task300Called = false + + queue.queue(100, TimeUnit.MILLISECONDS) { + task100Called = true + + queue.queue(100, TimeUnit.MILLISECONDS) { + task200Called = true + } + + } + + queue.queue(300, TimeUnit.MILLISECONDS) { + task300Called = true + } + + queue.tick(250) + assertThat(task100Called).isTrue() + assertThat(task200Called).isTrue() + assertThat(task300Called).isFalse() + } + + + @Test + internal fun `does not triggers nested work that is scheduled outside of the tick`() { + var task100Called = false + var task200Called = false + var task300Called = false + + queue.queue(100, TimeUnit.MILLISECONDS) { + task100Called = true + + queue.queue(100, TimeUnit.MILLISECONDS) { + task200Called = true + + queue.queue(100, TimeUnit.MILLISECONDS) { + task300Called = true + } + } + } + + queue.tick(250) + assertThat(task100Called).isTrue() + assertThat(task200Called).isTrue() + assertThat(task300Called).isFalse() + } + + @Test + internal fun `queueAtFixedRate repeats work`() { + var repeatTaskCallCount = 0 + + queue.queueAtFixedRate(100, 100, TimeUnit.MILLISECONDS) { + repeatTaskCallCount += 1 + } + + queue.tick(500) + assertThat(repeatTaskCallCount).isEqualTo(5) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchWorkItem.kt b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchWorkItem.kt new file mode 100644 index 0000000..0d0a03f --- /dev/null +++ b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchWorkItem.kt @@ -0,0 +1,33 @@ +package org.phoenixframework.queue + +import org.phoenixframework.DispatchWorkItem + +class ManualDispatchWorkItem( + private val runnable: () -> Unit, + var deadline: Long, + private val period: Long = 0 +) : DispatchWorkItem { + + private var performCount = 0 + + // Test + fun isPastDue(tickTime: Long): Boolean { + return this.deadline <= tickTime + } + + fun perform() { + if (isCancelled) return + runnable.invoke() + performCount += 1 + + // If the task is repeatable, then schedule the next deadline after the given period + deadline += period + } + + // DispatchWorkItem + override var isCancelled: Boolean = false + + override fun cancel() { + this.isCancelled = true + } +} diff --git a/src/test/kotlin/org/phoenixframework/utilities/TestUtilities.kt b/src/test/kotlin/org/phoenixframework/utilities/TestUtilities.kt index c68cb96..015e95e 100644 --- a/src/test/kotlin/org/phoenixframework/utilities/TestUtilities.kt +++ b/src/test/kotlin/org/phoenixframework/utilities/TestUtilities.kt @@ -2,111 +2,7 @@ package org.phoenixframework.utilities import org.phoenixframework.Binding import org.phoenixframework.Channel -import org.phoenixframework.DispatchQueue -import org.phoenixframework.DispatchWorkItem -import java.util.concurrent.TimeUnit -//------------------------------------------------------------------------------ -// Dispatch Queue -//------------------------------------------------------------------------------ -class ManualDispatchQueue : DispatchQueue { - - private var tickTime: Long = 0 - private val tickTimeUnit: TimeUnit = TimeUnit.MILLISECONDS - private var workItems: MutableList = mutableListOf() - - fun reset() { - this.tickTime = 0 - this.workItems = mutableListOf() - } - - fun tick(duration: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { - val durationInMs = tickTimeUnit.convert(duration, unit) - - // Advance the fake clock - this.tickTime += durationInMs - - // Filter all work items that are due to be fired and have not been - // cancelled. Return early if there are no items to fire - val pastDueWorkItems = workItems.filter { it.isPastDue(tickTime) && !it.isCancelled } - - // if no items are due, then return early - if (pastDueWorkItems.isEmpty()) return - - // Perform all work items that are due - pastDueWorkItems.forEach { it.perform() } - - // Remove all work items that are past due or canceled - workItems.removeAll { it.isPastDue(tickTime) || it.isCancelled } - } - - override fun queue(delay: Long, unit: TimeUnit, runnable: () -> Unit): DispatchWorkItem { - // Converts the given unit and delay to the unit used by this class - val delayInMs = tickTimeUnit.convert(delay, unit) - val deadline = tickTime + delayInMs - - val workItem = ManualDispatchWorkItem(runnable, deadline) - workItems.add(workItem) - - return workItem - } - - override fun queueAtFixedRate( - delay: Long, - period: Long, - unit: TimeUnit, - runnable: () -> Unit - ): DispatchWorkItem { - - val delayInMs = tickTimeUnit.convert(delay, unit) - val periodInMs = tickTimeUnit.convert(period, unit) - val deadline = tickTime + delayInMs - - val workItem = - ManualDispatchWorkItem(runnable, deadline, periodInMs) - workItems.add(workItem) - - return workItem - } -} - -//------------------------------------------------------------------------------ -// Work Item -//------------------------------------------------------------------------------ -class ManualDispatchWorkItem( - private val runnable: () -> Unit, - private var deadline: Long, - private val period: Long = 0 -) : DispatchWorkItem { - - private var performCount = 0 - - - // Test - fun isPastDue(tickTime: Long): Boolean { - return this.deadline <= tickTime - } - - fun perform() { - if (isCancelled) return - runnable.invoke() - performCount += 1 - - // If the task is repeatable, then schedule the next deadline after the given period - deadline += (performCount * period) - } - - // DispatchWorkItem - override var isCancelled: Boolean = false - - override fun cancel() { - this.isCancelled = true - } -} - -//------------------------------------------------------------------------------ -// Channel Extension -//------------------------------------------------------------------------------ fun Channel.getBindings(event: String): List { return bindings.toList().filter { it.event == event } } \ No newline at end of file From 3dae1de8bd026c289b4d55ce3e79e95ebeff07dd Mon Sep 17 00:00:00 2001 From: Daniel Rees Date: Thu, 20 Jun 2019 09:34:45 -0400 Subject: [PATCH 2/3] Added reset test --- .../queue/ManualDispatchQueue.kt | 4 +-- .../queue/ManualDispatchQueueTest.kt | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt index 2a79351..9d474a8 100644 --- a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt +++ b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueue.kt @@ -6,9 +6,9 @@ import java.util.concurrent.TimeUnit class ManualDispatchQueue : DispatchQueue { - private var tickTime: Long = 0 + var tickTime: Long = 0 private val tickTimeUnit: TimeUnit = TimeUnit.MILLISECONDS - private var workItems: MutableList = mutableListOf() + var workItems: MutableList = mutableListOf() fun reset() { this.tickTime = 0 diff --git a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt index 1cedc32..4a5705e 100644 --- a/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt +++ b/src/test/kotlin/org/phoenixframework/queue/ManualDispatchQueueTest.kt @@ -21,6 +21,32 @@ internal class ManualDispatchQueueTest { queue.reset() } + @Test + internal fun `reset the queue`() { + var task100Called = false + var task200Called = false + var task300Called = false + + queue.queue(100, TimeUnit.MILLISECONDS) { + task100Called = true + } + queue.queue(200, TimeUnit.MILLISECONDS) { + task200Called = true + } + queue.queue(300, TimeUnit.MILLISECONDS) { + task300Called = true + } + + queue.tick(250) + + assertThat(queue.tickTime).isEqualTo(250) + assertThat(queue.workItems).hasSize(1) + + queue.reset() + assertThat(queue.tickTime).isEqualTo(0) + assertThat(queue.workItems).isEmpty() + } + @Test internal fun `triggers work that is passed due`() { var task100Called = false @@ -92,6 +118,8 @@ internal class ManualDispatchQueueTest { assertThat(task100Called).isTrue() assertThat(task200Called).isTrue() assertThat(task300Called).isFalse() + + assertThat(queue.tickTime).isEqualTo(250) } From 9e713129d946e1b973226d13cb758d63560184e6 Mon Sep 17 00:00:00 2001 From: Daniel Rees Date: Thu, 20 Jun 2019 09:40:51 -0400 Subject: [PATCH 3/3] Updated channel tests to account for the timer behavior properly --- src/test/kotlin/org/phoenixframework/ChannelTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/org/phoenixframework/ChannelTest.kt b/src/test/kotlin/org/phoenixframework/ChannelTest.kt index 30b999a..2ff5b31 100644 --- a/src/test/kotlin/org/phoenixframework/ChannelTest.kt +++ b/src/test/kotlin/org/phoenixframework/ChannelTest.kt @@ -302,7 +302,7 @@ class ChannelTest { fakeClock.tick(6_000) whenever(socket.isConnected).thenReturn(true) - fakeClock.tick(5_000) + fakeClock.tick(4_000) joinPush.trigger("ok", kEmptyPayload) fakeClock.tick(2_000) @@ -334,7 +334,7 @@ class ChannelTest { } private fun receivesTimeout(joinPush: Push) { - fakeClock.tick(joinPush.timeout * 2) + fakeClock.tick(joinPush.timeout) } private fun receivesError(joinPush: Push) {