Skip to content

Commit

Permalink
SQ: Automated tests, that will run on each OS in CI automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
davidfstr committed Dec 16, 2023
1 parent 8c95d1c commit 9836246
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 9 deletions.
3 changes: 0 additions & 3 deletions src/crystal/browser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,6 @@ def start_server(self) -> 'ProjectServer':
"""
if self._project_server is None:
self._log_drawer = LogDrawer(parent=self._frame)

# TODO: If the server couldn't be started (ex: due to the default port being in
# use), report an appropriate error.
self._project_server = ProjectServer(self.project, stdout=self._log_drawer.writer)

return self._project_server
Expand Down
1 change: 1 addition & 0 deletions src/crystal/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self,
self._verbosity = verbosity
self._stdout = stdout

# Start server on port, looking for alternative open port if needed
while True:
try:
address = ('', port)
Expand Down
2 changes: 1 addition & 1 deletion src/crystal/tests/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _run_tests(test_names: List[str]) -> bool:
except SkipTest as e:
result_for_test_func_id[test_func_id] = e

print('SKIP')
print(f'SKIP ({str(e)})')
except Exception as e:
result_for_test_func_id[test_func_id] = e

Expand Down
79 changes: 76 additions & 3 deletions src/crystal/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
from copy import deepcopy
from crystal.model import Project, Resource, ResourceRevision, RootResource
from crystal import server
from crystal.server import get_request_url
from crystal.server import _DEFAULT_SERVER_PORT, get_request_url
from crystal.tests.util.controls import click_button, TreeItem
from crystal.tests.util.runner import bg_sleep
from crystal.tests.util.server import (
assert_does_open_webbrowser_to, extracted_project, fetch_archive_url, WebPage,
assert_does_open_webbrowser_to, extracted_project, fetch_archive_url,
served_project, WebPage,
)
from crystal.tests.util.skip import skipTest
from crystal.tests.util.tasks import wait_for_download_to_start_and_finish
from crystal.tests.util.wait import DEFAULT_WAIT_PERIOD
from crystal.tests.util.windows import MainWindow, OpenOrCreateDialog
from crystal.tests.util.windows import AddUrlDialog, MainWindow, OpenOrCreateDialog
from io import StringIO
import socket
import tempfile
from typing import AsyncIterator, Callable, Optional, Tuple
from unittest import skip
Expand All @@ -20,6 +24,60 @@
# in test_workflows.py. Write stubs for all such behaviors
# and link them to the covering test.

# ------------------------------------------------------------------------------
# Test: Start Server

async def test_given_default_serving_port_in_use_when_start_serving_project_then_finds_alternate_port() -> None:
LOCALHOST = '127.0.0.1'

if _is_port_in_use(LOCALHOST, _DEFAULT_SERVER_PORT):
skipTest('_DEFAULT_SERVER_PORT is already in use outside of tests')
if _is_port_in_use(LOCALHOST, _DEFAULT_SERVER_PORT + 1):
skipTest('_DEFAULT_SERVER_PORT + 1 is already in use outside of tests')

with served_project('testdata_xkcd.crystalproj.zip', port=_DEFAULT_SERVER_PORT) as sp:
assert _is_port_in_use(LOCALHOST, _DEFAULT_SERVER_PORT)

# Define URLs
home_url = sp.get_request_url('https://xkcd.com/')

with tempfile.TemporaryDirectory(suffix='.crystalproj') as project_dirpath:
async with (await OpenOrCreateDialog.wait_for()).create(project_dirpath) as (mw, _):
project = Project._last_opened_project
assert project is not None

# Create a URL
if True:
root_ti = TreeItem.GetRootItem(mw.entity_tree.window)
assert root_ti is not None
assert root_ti.GetFirstChild() is None # no entities

click_button(mw.add_url_button)
aud = await AddUrlDialog.wait_for()

aud.name_field.Value = 'Home'
aud.url_field.Value = home_url
await aud.ok()
(home_ti,) = root_ti.Children

# Download the URL
home_ti.SelectItem()
await mw.click_download_button()
await wait_for_download_to_start_and_finish(mw.task_tree)

# Try to start second server, also on _DEFAULT_SERVER_PORT.
# Expect it to actually start on (_DEFAULT_SERVER_PORT + 1).
expected_port = _DEFAULT_SERVER_PORT + 1
home_ti.SelectItem()
with assert_does_open_webbrowser_to(get_request_url(home_url, expected_port)):
click_button(mw.view_button)
assert _is_port_in_use(LOCALHOST, expected_port)

# Ensure can fetch the revision through the server
server_page = await fetch_archive_url(home_url, expected_port)
assert 200 == server_page.status


# ------------------------------------------------------------------------------
# Test: Header Inclusion & Exclusion

Expand Down Expand Up @@ -156,4 +214,19 @@ async def serve_and_fetch_xkcd_home_page(mw: MainWindow) -> Tuple[WebPage, str]:
return (server_page, captured_stdout.getvalue())


def _is_port_in_use(hostname: str, port: int) -> bool:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
try:
result = s.connect_ex((hostname, port))
except:
raise ValueError(f'socket.connect_ex({(hostname, port)}) failed')
if result == 0:
return True
else:
return False
finally:
s.close()


# ------------------------------------------------------------------------------
6 changes: 4 additions & 2 deletions src/crystal/tests/util/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
def served_project(
zipped_project_filename: str,
*, fetch_date_of_resources_set_to: Optional[datetime.datetime]=None,
port: Optional[int]=None,
) -> Iterator[ProjectServer]:
if fetch_date_of_resources_set_to is not None:
if not datetime_is_aware(fetch_date_of_resources_set_to):
Expand Down Expand Up @@ -67,7 +68,7 @@ def fg_task() -> None:

# Start server
project_server = ProjectServer(project,
port=2798, # CRYT on telephone keypad
port=(port or 2798), # CRYT on telephone keypad
verbosity='indent',
)
yield project_server
Expand Down Expand Up @@ -114,12 +115,13 @@ async def is_url_not_in_archive(archive_url: str) -> bool:

async def fetch_archive_url(
archive_url: str,
port: Optional[int]=None,
*, headers: Optional[Dict[str, str]]=None,
timeout: Optional[float]=None,
) -> WebPage:
if timeout is None:
timeout = DEFAULT_WAIT_TIMEOUT
return await bg_fetch_url(get_request_url(archive_url), headers=headers, timeout=timeout)
return await bg_fetch_url(get_request_url(archive_url, port), headers=headers, timeout=timeout)


class WebPage:
Expand Down

0 comments on commit 9836246

Please sign in to comment.