Skip to content

Commit

Permalink
Add a test for debounce
Browse files Browse the repository at this point in the history
  • Loading branch information
msullivan committed Mar 13, 2024
1 parent 9813f95 commit be797d3
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 4 deletions.
9 changes: 5 additions & 4 deletions edb/common/asyncutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from typing import Callable, TypeVar, Awaitable

import asyncio
import time


_T = TypeVar('_T')
Expand Down Expand Up @@ -94,7 +93,7 @@ async def debounce(
loop = asyncio.get_running_loop()

batch = []
last_signal = 0.0
last_signal = -MAX_WAIT
target_time = None

while True:
Expand All @@ -120,9 +119,11 @@ async def debounce(
# not longer than MAX_WAIT.
elif (
target_time is not None
and target_time < last_signal + MAX_WAIT
):
target_time = max(t + DELAY_AMT, target_time)
target_time = min(
max(t + DELAY_AMT, target_time),
last_signal + MAX_WAIT,
)

# Skip sending the event if we need to wait longer.
if (
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ test = [
'ruff~=0.1.6',
'asyncpg~=0.29.0',

# Needed for testing asyncutil
'async_solipsism==0.5.0',

# Needed for test_docs_sphinx_ext
'requests-xml~=0.2.3',

Expand Down
121 changes: 121 additions & 0 deletions tests/common/test_asyncutil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#
# This source file is part of the EdgeDB open source project.
#
# Copyright 2016-present MagicStack Inc. and the EdgeDB authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


import asyncio
import unittest

from edb.common import asyncutil

try:
import async_solipsism
except ImportError:
async_solipsism = None # type: ignore


def with_fake_event_loop(f):
# async_solpsism creates an event loop with, among other things,
# a totally fake clock.
def new(*args, **kwargs):
loop = async_solipsism.EventLoop()
try:
loop.run_until_complete(f(*args, **kwargs))
finally:
loop.close()

return new


@unittest.skipIf(async_solipsism is None, 'async_solipsism is missing')
class TestDebounce(unittest.TestCase):

@with_fake_event_loop
async def test_debounce_01(self):
loop = asyncio.get_running_loop()
outs = []
ins = asyncio.Queue()

async def output(vs):
assert loop.time() == int(loop.time())
outs.append((int(loop.time()), vs))

async def sleep_until(t):
await asyncio.sleep(t - loop.time())

task = asyncio.create_task(asyncutil.debounce(
ins.get,
output,
# Use integers for delays to avoid any possibility of
# floating point nonsense
max_wait=500,
delay_amt=200,
max_batch_size=4,
))

ins.put_nowait(1)
await sleep_until(10)
ins.put_nowait(2)
ins.put_nowait(3)
await sleep_until(300)
ins.put_nowait(4)
ins.put_nowait(5)
ins.put_nowait(6)
await sleep_until(1000)

# Time 1000 now
ins.put_nowait(7)
await sleep_until(1150)
ins.put_nowait(8)
ins.put_nowait(9)
ins.put_nowait(10)
await sleep_until(1250)
ins.put_nowait(11)

ins.put_nowait(12)
await asyncio.sleep(190)
ins.put_nowait(13)
await asyncio.sleep(190)
ins.put_nowait(14)
await asyncio.sleep(190)
self.assertEqual(loop.time(), 1820)
ins.put_nowait(15)

# Make sure everything clears out and stop it
await asyncio.sleep(10000)
task.cancel()

self.assertEqual(
outs,
[
# First one right away
(0, [1]),
# Next two added at 10 + 200 tick
(210, [2, 3]),
# Next three added at 300 + 200 tick
(500, [4, 5, 6]),
# First at 1000
(1000, [7]),
# Next group at 1250 when the batch fills up
(1250, [8, 9, 10, 11]),
# And more at 1750 when time expires on that batch
(1750, [12, 13, 14]),
# And the next one (queued at 1820) at 200 after it was queued,
# since there had been a recent signal when it was queued.
(2020, [15]),
],
)

0 comments on commit be797d3

Please sign in to comment.