Skip to content

Commit

Permalink
Merge pull request #1156 from finos/tornado-tests
Browse files Browse the repository at this point in the history
Submit tornado write_message to IOLoop, add websocket tests in Python
  • Loading branch information
texodus authored Aug 26, 2020
2 parents cf49688 + caed681 commit 823bede
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cpp/perspective/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD)
find_package( Python ${PSP_PYTHON_VERSION} REQUIRED COMPONENTS Interpreter Development)
link_directories( ${Python_LIBRARY_DIRS} )
endif()
message("${Cyan}Using Python_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}, Python_LIBRARIES: ${Python_LIBRARIES} ${ColorReset}")
message("${Cyan}Using Python ${Python_VERSION}, \nPython_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}, \nPython_LIBRARIES: ${Python_LIBRARIES}, \nPython_EXECUTABLE: ${Python_EXECUTABLE} ${ColorReset}")
include_directories( ${Python_INCLUDE_DIRS} )

if(MACOS)
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/examples/remote.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script>
<script src="https://unpkg.com/@finos/perspective"></script>

<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.dark.css">
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.css">

<style>
perspective-viewer{position:absolute;top:0;left:0;right:0;bottom:0;}
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/examples/streaming.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script>
<script src="https://unpkg.com/@finos/perspective"></script>

<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.dark.css">
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.css">


<style>
Expand Down
7 changes: 7 additions & 0 deletions python/perspective/perspective/tests/table/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
################################################################################
#
# Copyright (c) 2019, the Perspective Authors.
#
# This file is part of the Perspective library, distributed under the terms of
# the Apache License 2.0. The full license can be found in the LICENSE file.
#
import json
import random
import pytest

import tornado
from tornado import gen
from tornado.websocket import websocket_connect
from datetime import datetime

from ...table import Table
from ...manager import PerspectiveManager
from ...manager.manager_internal import DateTimeEncoder
from ...tornado_handler import PerspectiveTornadoHandler


data = {
"a": [i for i in range(10)],
"b": [i * 1.5 for i in range(10)],
"c": [str(i) for i in range(10)],
"d": [datetime(2020, 3, i, i, 30, 45) for i in range(1, 11)],
}

MANAGER = PerspectiveManager()

APPLICATION = tornado.web.Application(
[
(
r"/websocket",
PerspectiveTornadoHandler,
{"manager": MANAGER, "check_origin": True},
)
]
)


@pytest.fixture(scope="module")
def app():
return APPLICATION


class TestPerspectiveTornadoHandler(object):
def setup_method(self):
"""Flush manager state before each test method execution."""
MANAGER._tables = {}
MANAGER._views = {}

@gen.coroutine
def websocket_client(self, port):
"""Connect and initialize a websocket client connection to the
Perspective tornado server.
If the initialization response is incorrect, raise an `AssertionError`.
"""
client = yield websocket_connect(
"ws://127.0.0.1:{0}/websocket".format(port)
)

yield client.write_message(json.dumps({"id": -1, "cmd": "init"}))

response = yield client.read_message()

assert json.loads(response) == {"id": -1, "data": None}

# Compatibility with Python < 3.3
raise gen.Return(client)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_init(self, app, http_client, http_port):
"""Using Tornado's websocket client, test the websocket provided by
PerspectiveTornadoHandler.
All test methods must import `app`, `http_client`, and `http_port`,
otherwise a mysterious timeout will occur."""
yield self.websocket_client(http_port)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_table_method(self, app, http_client, http_port):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"name": table_name,
"cmd": "table_method",
"method": "schema",
"args": [],
}
)
)

response = yield client.read_message()

assert json.loads(response) == {
"id": 0,
"data": {
"a": "integer",
"b": "float",
"c": "string",
"d": "datetime",
},
}

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_dict(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_dict",
"args": [],
}
)
)

response = yield client.read_message()

assert response == json.dumps(
{"id": 1, "data": data}, cls=DateTimeEncoder
)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_dict_one_sided(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
"config": {
"row_pivots": ["c"]
}
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_dict",
"args": [],
}
)
)

response = yield client.read_message()

assert response == json.dumps(
{
"id": 1,
"data": {
"__ROW_PATH__": [[]] + [[item] for item in data["c"]],
"a": [sum(data["a"])] + data["a"],
"b": [sum(data["b"])] + data["b"],
"c": [10] + [1 for i in range(10)],
"d": [10] + [1 for i in range(10)],
}
}, cls=DateTimeEncoder
)

@pytest.mark.gen_test(run_sync=False)
def test_tornado_handler_create_view_to_arrow(
self, app, http_client, http_port
):
table_name = str(random.random())
table = Table(data)
MANAGER.host_table(table_name, table)

client = yield self.websocket_client(http_port)

yield client.write_message(
json.dumps(
{
"id": 0,
"cmd": "view",
"table_name": table_name,
"view_name": "view1",
}
)
)

yield client.write_message(
json.dumps(
{
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_arrow",
"args": [],
}
)
)

response = yield client.read_message()

assert json.loads(response) == {
"id": 1,
"name": "view1",
"cmd": "view_method",
"method": "to_arrow",
"args": [],
"is_transferable": True
}

binary = yield client.read_message()
new_table = Table(binary)
assert new_table.view().to_dict() == data
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def post(self, message, binary=False):
message (:obj:`str`): a JSON-serialized string containing a message to the
front-end `perspective-viewer`.
'''
self.write_message(message, binary)
loop = IOLoop.current()
loop.add_callback(self.write_message, message, binary)

def on_close(self):
'''Remove the views associated with the client when the websocket
Expand Down
1 change: 1 addition & 0 deletions python/perspective/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
'pytest>=4.3.0',
'pytest-cov>=2.6.1',
'pytest-check-links',
'pytest-tornado',
'pytz>=2018.9',
'Sphinx>=1.8.4',
'sphinx-markdown-builder>=0.5.2',
Expand Down

0 comments on commit 823bede

Please sign in to comment.