Skip to content

Commit

Permalink
Fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ahuang11 committed Dec 5, 2023
1 parent 30e9891 commit 3f7d9f3
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 82 deletions.
14 changes: 4 additions & 10 deletions panel/chat/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import traceback

from functools import partial
from inspect import (
isasyncgen, isasyncgenfunction, isawaitable, isgenerator,
)
from inspect import isasyncgen, isawaitable, isgenerator
from io import BytesIO
from typing import (
TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Literal,
Expand Down Expand Up @@ -432,7 +430,7 @@ async def _prepare_response(self, _) -> None:

disabled = self.disabled
try:
with param.batch_watch(self):
with param.parameterized.batch_call_watchers(self):
self.disabled = True
self._callback_is_running = True

Expand All @@ -444,11 +442,7 @@ async def _prepare_response(self, _) -> None:
loop = asyncio.get_event_loop()
contents = self._extract_contents(message)

is_async = (
asyncio.iscoroutinefunction(self.callback) or
isasyncgenfunction(self.callback)
) # noqa: E501
if is_async:
if asyncio.iscoroutinefunction(self.callback):
future = loop.create_task(self._wrap_async_callback(contents, message))
else:
future = loop.run_in_executor(None, partial(self.callback, contents, message.user, self))
Expand All @@ -472,7 +466,7 @@ async def _prepare_response(self, _) -> None:
else:
raise e
finally:
with param.batch_watch(self):
with param.parameterized.batch_call_watchers(self):
self._replace_placeholder(None)
self.disabled = disabled
self._callback_is_running = False
Expand Down
77 changes: 22 additions & 55 deletions panel/tests/chat/test_feed.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import asyncio
import time

from unittest.mock import MagicMock

import pytest

from panel.chat.feed import ChatFeed
from panel.chat.message import ChatMessage
from panel.layout import Column, Row
from panel.pane.image import Image
from panel.pane.markup import HTML
from panel.tests.util import wait_until
from panel.tests.util import async_wait_until, wait_until
from panel.widgets.indicators import LinearGauge
from panel.widgets.input import TextAreaInput, TextInput

Expand Down Expand Up @@ -343,7 +341,8 @@ def test_default_avatars_message_params(self, chat_feed):
def test_no_recursion_error(self, chat_feed):
chat_feed.send("Some time ago, there was a recursion error like this")

def test_chained_response(self, chat_feed):
@pytest.mark.asyncio
async def test_chained_response(self, chat_feed):
async def callback(contents, user, instance):
if user == "User":
yield {
Expand All @@ -363,7 +362,7 @@ async def callback(contents, user, instance):

chat_feed.callback = callback
chat_feed.send("Testing!", user="User")
wait_until(lambda: len(chat_feed.objects) == 3)
await async_wait_until(lambda: len(chat_feed.objects) == 3)
assert chat_feed.objects[1].user == "arm"
assert chat_feed.objects[1].avatar == "🦾"
assert chat_feed.objects[1].object == "Hey, leg! Did you hear the user?"
Expand Down Expand Up @@ -529,100 +528,69 @@ async def echo(contents, user, instance):

def test_placeholder_disabled(self, chat_feed):
def echo(contents, user, instance):
time.sleep(0.25)
yield "hey testing"
time.sleep(1.25)
assert instance._placeholder not in instance._chat_log
return "hey testing"

chat_feed.placeholder_threshold = 0
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
# only append sent message
assert chat_feed.append.call_count == 2
assert chat_feed._placeholder not in chat_feed._chat_log

def test_placeholder_enabled(self, chat_feed):
def echo(contents, user, instance):
time.sleep(0.25)
yield "hey testing"
time.sleep(1.25)
assert instance._placeholder in instance._chat_log
return chat_feed.stream("hey testing")

chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed._placeholder not in chat_feed._chat_log
# append sent message and placeholder
assert chat_feed.append.call_args_list[1].args[0] == chat_feed._placeholder

def test_placeholder_threshold_under(self, chat_feed):
async def echo(contents, user, instance):
await asyncio.sleep(0.25)
assert instance._placeholder not in instance._chat_log
return "hey testing"

chat_feed.placeholder_threshold = 5
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed.append.call_args_list[1].args[0] != chat_feed._placeholder
assert chat_feed._placeholder not in chat_feed._chat_log

def test_placeholder_threshold_under_generator(self, chat_feed):
async def echo(contents, user, instance):
assert instance._placeholder not in instance._chat_log
await asyncio.sleep(0.25)
assert instance._placeholder not in instance._chat_log
yield "hey testing"

chat_feed.placeholder_threshold = 5
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed.append.call_args_list[1].args[0] != chat_feed._placeholder

def test_placeholder_threshold_exceed(self, chat_feed):
async def echo(contents, user, instance):
await asyncio.sleep(0.5)
yield "hello testing"
assert instance._placeholder in instance._chat_log
return "hello testing"

chat_feed.placeholder_threshold = 0.1
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed.append.call_args_list[1].args[0] == chat_feed._placeholder
assert chat_feed._placeholder not in chat_feed._chat_log

def test_placeholder_threshold_exceed_generator(self, chat_feed):
async def echo(contents, user, instance):
await asyncio.sleep(0.5)
assert instance._placeholder in instance._chat_log
yield "hello testing"

chat_feed.placeholder_threshold = 0.1
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed.append.call_args_list[1].args[0] == chat_feed._placeholder

def test_placeholder_threshold_sync(self, chat_feed):
"""
Placeholder should always be appended if the
callback is synchronous.
"""

def echo(contents, user, instance):
time.sleep(0.25)
yield "hey testing"

chat_feed.placeholder_threshold = 5
chat_feed.callback = echo
chat_feed.append = MagicMock(
side_effect=lambda message: chat_feed._chat_log.append(message)
)
chat_feed.send("Message", respond=True)
assert chat_feed.append.call_args_list[1].args[0] == chat_feed._placeholder
assert chat_feed._placeholder not in chat_feed._chat_log

def test_renderers_pane(self, chat_feed):
chat_feed.renderers = [HTML]
Expand Down Expand Up @@ -707,8 +675,7 @@ async def callback(msg, user, instance):
yield "B" # should not reach this point

chat_feed.callback = callback
with pytest.raises(asyncio.CancelledError):
chat_feed.send("Message", respond=True)
chat_feed.send("Message", respond=True)
assert chat_feed.objects[-1].object == "A"

def test_callback_stop_not_async(self, chat_feed):
Expand Down
32 changes: 15 additions & 17 deletions panel/tests/chat/test_interface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@


import asyncio
import time

from io import BytesIO

Expand Down Expand Up @@ -89,11 +88,17 @@ def test_click_send(self, chat_interface: ChatInterface):
chat_interface._click_send(None)
assert len(chat_interface.objects) == 1

def test_not_show_stop(self, chat_interface: ChatInterface):
def test_show_stop_disabled(self, chat_interface: ChatInterface):
async def callback(msg, user, instance):
yield "A"
send_button = chat_interface._input_layout[1]
stop_button = chat_interface._input_layout[2]
assert send_button.name == "Send"
assert stop_button.name == "Stop"
assert send_button.visible
assert not stop_button.visible
await asyncio.sleep(2)
yield "B" # should not reach this point
yield "B" # should not stream this

chat_interface.callback = callback
chat_interface.show_stop = False
Expand All @@ -105,36 +110,29 @@ async def callback(msg, user, instance):
assert send_button.visible
assert not stop_button.visible

def test_click_stop_for_async(self, chat_interface: ChatInterface):
def test_show_stop_for_async(self, chat_interface: ChatInterface):
async def callback(msg, user, instance):
yield "A"
send_button = instance._input_layout[1]
stop_button = instance._input_layout[2]
assert send_button.name == "Send"
assert stop_button.name == "Stop"
assert instance._click_stop(None)
await asyncio.sleep(2)
yield "B" # should not reach this point
assert not send_button.visible
assert stop_button.visible

chat_interface.callback = callback
with pytest.raises(asyncio.CancelledError):
chat_interface.send("Message", respond=True)
assert chat_interface.objects[-1].object == "A"
chat_interface.send("Message", respond=True)

def test_click_stop_for_sync(self, chat_interface: ChatInterface):
def test_show_stop_for_sync(self, chat_interface: ChatInterface):
def callback(msg, user, instance):
yield "A"
send_button = instance._input_layout[1]
stop_button = instance._input_layout[2]
assert send_button.name == "Send"
assert stop_button.name == "Stop"
instance._click_stop(None)
time.sleep(2)
yield "B"
assert not send_button.visible
assert stop_button.visible

chat_interface.callback = callback
chat_interface.send("Message", respond=True)
assert chat_interface.objects[-1].object == "A"

@pytest.mark.parametrize("widget", [TextInput(), TextAreaInput()])
def test_auto_send_types(self, chat_interface: ChatInterface, widget):
Expand Down
72 changes: 72 additions & 0 deletions panel/tests/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import contextlib
import os
import platform
Expand Down Expand Up @@ -192,6 +193,77 @@ def timed_out():
time.sleep(interval / 1000)


async def async_wait_until(fn, page=None, timeout=5000, interval=100):
"""
Exercise a test function in a loop until it evaluates to True
or times out.
The function can either be a simple lambda that returns True or False:
>>> await async_wait_until(lambda: x.values() == ['x'])
Or a defined function with an assert:
>>> async def _()
>>> assert x.values() == ['x']
>>> await async_wait_until(_)
In a Playwright context test, you should pass the page fixture:
>>> await async_wait_until(lambda: x.values() == ['x'], page)
Parameters
----------
fn : callable
Callback
page : playwright.async_api.Page, optional
Playwright page
timeout : int, optional
Total timeout in milliseconds, by default 5000
interval : int, optional
Waiting interval, by default 100
Adapted from pytest-qt.
"""
# Hide this function traceback from the pytest output if the test fails
__tracebackhide__ = True

start = time.time()

def timed_out():
elapsed = time.time() - start
elapsed_ms = elapsed * 1000
return elapsed_ms > timeout

timeout_msg = f"wait_until timed out in {timeout} milliseconds"

while True:
try:
result = fn()
if asyncio.iscoroutine(result):
result = await result
except AssertionError as e:
if timed_out():
raise TimeoutError(timeout_msg) from e
else:
if result not in (None, True, False):
raise ValueError(
"`wait_until` callback must return None, True, or "
f"False, returned {result!r}"
)
# None is returned when the function has an assert
if result is None:
return
# When the function returns True or False
if result:
return
if timed_out():
raise TimeoutError(timeout_msg)
if page:
# Playwright recommends against using time.sleep
# https://playwright.dev/python/docs/intro#timesleep-leads-to-outdated-state
await page.wait_for_timeout(interval)
else:
await asyncio.sleep(interval / 1000)


def get_ctrl_modifier():
"""
Get the CTRL modifier on the current platform.
Expand Down

0 comments on commit 3f7d9f3

Please sign in to comment.