Skip to content

Commit

Permalink
Merge 4b6eeb6 into ca09c07
Browse files Browse the repository at this point in the history
  • Loading branch information
touilleMan committed Mar 17, 2019
2 parents ca09c07 + 4b6eeb6 commit f71f79c
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 15 deletions.
22 changes: 15 additions & 7 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,22 @@ build_script:
- SET PATH=%PYTHON%;%PYTHON%\Scripts;;%PATH%
- python --version
- python -c "import struct; print(struct.calcsize('P') * 8)"
- pip install -U coveralls
- pip install .[core,backend,dev]
- pip install -U wheel
- python setup.py bdist_wheel
# TODO: insert a sarcastic comment about lacking wildcard support, powershell
# treating stderr output as error, impossibility to set variable from command
# output and how unjustified windows crazinesses waste my time in general
- python -c "import os, glob; cmd = 'pip install {}[core,backend,dev]'.format(glob.glob('dist/parsec_cloud-*.whl')[0]); print(cmd); raise SystemExit(os.system(cmd))"

test_script:
# Uncomment this for RDP debug
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
- py.test tests/ -n 2 --cov=parsec --cov-config=setup.cfg --runslow --runmountpoint -x --postgresql --log-level=DEBUG -vvv

# TODO: re-enable me !
# on_success:
# - coveralls
- mkdir empty
- cd empty
# No sed ? Hold my beer !
- python -c "from pathlib import Path; fd = Path('../setup.cfg'); fd.write_text(fd.read_text().replace('parsec/core/gui/', '../parsec/core/gui/'))"
- py.test ../tests/ -n 2 --cov=parsec --cov-config=../setup.cfg --runslow -x --postgresql --log-level=DEBUG -vvv -x

on_success:
- ps: if ($COVERALLS_REPO_TOKEN -ne "") { pip install -U --user coveralls }
- ps: if ($COVERALLS_REPO_TOKEN -ne "") { python -m coveralls }
16 changes: 11 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@ python:
#- '3.7-dev'
services:
- postgresql
- xvfb
addons:
postgresql: "9.5"
apt:
packages:
- postgresql-server-dev-9.5
install:
- pip install -U coveralls
- pip install .[all]
- pip install -U coveralls wheel
- python setup.py bdist_wheel
- pip install $(ls dist/parsec_cloud-*.whl)[all]
before_script:
- sudo modprobe fuse
script:
- set -e # stop build as soon as a command fails
# Only print output if check failed
- OUT=$(2>&1 ./misc/autoformat.sh --check) || (echo $OUT; $(exit 1))
- flake8
- flake8 setup.py parsec tests
- ./misc/license_headers.py check
- py.test tests -n 2 --cov=parsec --cov-config=setup.cfg --runmountpoint --log-level=DEBUG -vvv
- py.test tests -n 2 --cov=parsec --cov-config=setup.cfg --runslow --postgresql --log-level=DEBUG -vvv
- mkdir empty && cd empty
- sed -i 's#parsec/core/gui/#../parsec/core/gui/#' ../setup.cfg # fix path
- py.test ../tests -n 2 --cov=parsec --cov-config=../setup.cfg --runmountpoint --log-level=DEBUG -vvv -x
- py.test ../tests -n 2 --cov=parsec --cov-config=../setup.cfg --runslow --postgresql --log-level=DEBUG -vvv -x
- set +e
after_success:
- coveralls
deploy:
Expand Down
17 changes: 17 additions & 0 deletions misc/license_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
HEADER_RE = re.compile(
r"^# Parsec Cloud \(https://parsec.cloud\) Copyright \(c\) AGPLv3 2019 Scille SAS$"
)
SKIP_PATHES = (
pathlib.Path("parsec/core/gui/_resources_rc.py"),
pathlib.Path("parsec/core/gui/ui/"),
)


def need_skip(path):
for skip_path in SKIP_PATHES:
try:
path.relative_to(skip_path)
return True

except ValueError:
pass
return False


def get_files():
Expand All @@ -19,6 +34,8 @@ def get_files():
path = pathlib.Path(scan)
if path.is_dir():
for f in pathlib.Path(path).glob("**/*.py"):
if need_skip(f):
continue
yield f
elif path.is_file():
yield path
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ max-line-length = 100
exclude = .git,docs,restkit/compat.py,env,venv,.ropeproject,_sandbox,.tox,*_pb2.py,parsec/core/gui/_resources_rc.py,parsec/core/gui/ui/,misc/bench.py,bdb.py,.eggs

[coverage:run]
parallel=True
source=parsec
omit = parsec/core/gui/*

[coverage:report]
Expand Down
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ def Executable(x, **kw):
def fix_pyqt_import():
# PyQt5-sip is a distinct pip package that provides PyQt5.sip
# However it setuptools handles `setup_requires` by downloading the
# dependencies in the `./.eggs` directory wihtout really installing
# dependencies in the `./.eggs` directory without really installing
# them. This causes `import PyQt5.sip` to fail given the `PyQt5` folder
# doesn't contains `sip.so`...
# doesn't contains `sip.so` (or `sip.pyd` on windows)...
import sys
import glob
import importlib

for module_name, path_glob in (
("PyQt5", ".eggs/*PyQt5*/PyQt5/__init__.py"),
("PyQt5.sip", ".eggs/*PyQt5_sip*/PyQt5/sip.so"),
("PyQt5.sip", ".eggs/*PyQt5_sip*/PyQt5/sip.*"),
):
# If the module has already been installed in the environment
# setuptools won't populate the `.eggs` directory and we have
Expand Down Expand Up @@ -245,6 +245,8 @@ def _extract_libs_cffi_backend():
"pytest-cov",
"pytest-xdist",
"pytest-trio>=0.5.1",
"pytest-qt",
"pluggy==0.7.1", # see https://github.com/pytest-dev/pytest/issues/3753
"tox",
"wheel",
"Sphinx",
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def pytest_addoption(parser):
)
parser.addoption("--runslow", action="store_true", help="Don't skip slow tests")
parser.addoption("--runmountpoint", action="store_true", help="Don't skip FUSE/WinFSP tests")
parser.addoption("--rungui", action="store_true", help="Don't skip GUI tests")
parser.addoption(
"--realcrypto", action="store_true", help="Don't mock crypto operation to save time"
)
Expand Down Expand Up @@ -97,6 +98,9 @@ def pytest_runtest_setup(item):
pytest.skip("need --runmountpoint option to run")
elif not get_mountpoint_runner():
pytest.skip("FUSE/WinFSP not available")
if item.get_closest_marker("gui"):
if not item.config.getoption("--rungui"):
pytest.skip("need --rungui option to run")


@pytest.fixture(autouse=True, scope="session", name="unmock_crypto")
Expand Down
1 change: 1 addition & 0 deletions tests/core/mountpoint/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(self, base_mountpoint):
self.base_mountpoint = pathlib.Path(base_mountpoint)
# Provide a default workspace given we cannot create it through FUSE
self.default_workspace_name = "w"
self._task = None
self._portal = None
self._need_stop = trio.Event()
self._stopped = trio.Event()
Expand Down
1 change: 1 addition & 0 deletions tests/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Parsec Cloud (https://parsec.cloud) Copyright (c) AGPLv3 2019 Scille SAS
133 changes: 133 additions & 0 deletions tests/gui/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Parsec Cloud (https://parsec.cloud) Copyright (c) AGPLv3 2019 Scille SAS

import pytest
import trio
import threading
from inspect import iscoroutinefunction
from contextlib import contextmanager

from parsec.utils import start_task


@pytest.fixture
def backend_service_factory(backend_factory):
"""
Run a trio loop with backend in a separate thread to allow blocking
operations from the Qt loop.
"""

class BackendService:
def __init__(self):
self.port = None
self._portal = None
self._task = None
self._need_stop = trio.Event()
self._stopped = trio.Event()
self._need_start = trio.Event()
self._started = trio.Event()
self._ready = threading.Event()

def get_url(self):
if self.port is None:
raise RuntimeError("Port not yet available, is service started ?")
return f"ws://127.0.0.1:{self.port}"

def execute(self, cb):
if iscoroutinefunction(cb):
self._portal.run(cb, self._task.value)
else:
self._portal.run_sync(cb, self._task.value)

def start(self, **backend_factory_params):
async def _start():
self._need_start.set()
await self._started.wait()

self._backend_factory_params = backend_factory_params
self._portal.run(_start)

def stop(self):
async def _stop():
if self._started.is_set():
self._need_stop.set()
await self._stopped.wait()

self._portal.run(_stop)

def init(self):
self._thread = threading.Thread(target=trio.run, args=(self._service,))
self._thread.setName("BackendService")
self._thread.start()
self._ready.wait()

async def _teardown(self):
self._nursery.cancel_scope.cancel()

def teardown(self):
if not self._portal:
return
self.stop()
self._portal.run(self._teardown)
self._thread.join()

async def _service(self):
self._portal = trio.BlockingTrioPortal()

async def _backend_controlled_cb(*, task_status=trio.TASK_STATUS_IGNORED):
async with backend_factory(**self._backend_factory_params) as backend:
async with trio.open_nursery() as nursery:
listeners = await nursery.start(trio.serve_tcp, backend.handle_client, 0)
self.port = listeners[0].socket.getsockname()[1]
task_status.started(backend)
await trio.sleep_forever()

async with trio.open_nursery() as self._nursery:
self._ready.set()
while True:
await self._need_start.wait()
self._need_stop.clear()
self._stopped.clear()

self._task = await start_task(self._nursery, _backend_controlled_cb)
self._started.set()

await self._need_stop.wait()
self._need_start.clear()
self._started.clear()
await self._task.cancel_and_join()
self._stopped.set()

@contextmanager
def _backend_service_factory():
backend_service = BackendService()
backend_service.init()
try:
yield backend_service

finally:
backend_service.teardown()

return _backend_service_factory


@pytest.fixture
def backend_service(backend_service_factory):
with backend_service_factory() as backend:
yield backend


@pytest.fixture
def autoclose_dialog(monkeypatch):
class DialogSpy:
def __init__(self):
self.dialogs = []

spy = DialogSpy()

def _dialog_exec(dialog):
spy.dialogs.append((dialog.label_title.text(), dialog.label_message.text()))

monkeypatch.setattr(
"parsec.core.gui.custom_widgets.MessageDialog.exec_", _dialog_exec, raising=False
)
yield spy
52 changes: 52 additions & 0 deletions tests/gui/test_bootstrap_organization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Parsec Cloud (https://parsec.cloud) Copyright (c) AGPLv3 2019 Scille SAS

import pytest
from PyQt5 import QtCore

from parsec.types import BackendOrganizationBootstrapAddr
from parsec.core.gui.main_window import MainWindow


@pytest.mark.gui
def test_bootstrap_organization(qtbot, autoclose_dialog, backend_service, core_config):
backend_service.start(populated=False)

org_id = "NewOrg"
org_token = "123456"
user_id = "Zack"
device_name = "pc1"
password = "S3cr3tP@ss"
organization_addr = BackendOrganizationBootstrapAddr.build(
backend_service.get_url(), org_id, org_token
)

# Create organization in the backend

async def _create_organization(backend):
await backend.organization.create(org_id, org_token)

backend_service.execute(_create_organization)

# Start GUI

main_w = MainWindow(core_config)
qtbot.addWidget(main_w)
login_w = main_w.login_widget

# Go do the bootstrap

assert not login_w.bootstrap_organization.isVisible()
qtbot.mouseClick(login_w.button_bootstrap_instead, QtCore.Qt.LeftButton)
# assert login_w.bootstrap_organization.isVisible()

qtbot.keyClicks(login_w.bootstrap_organization.line_edit_login, user_id)
qtbot.keyClicks(login_w.bootstrap_organization.line_edit_password, password)
qtbot.keyClicks(login_w.bootstrap_organization.line_edit_password_check, password)
qtbot.keyClicks(login_w.bootstrap_organization.line_edit_url, organization_addr)
qtbot.keyClicks(login_w.bootstrap_organization.line_edit_device, device_name)

with qtbot.waitSignal(login_w.bootstrap_organization.organization_bootstrapped):
qtbot.mouseClick(login_w.bootstrap_organization.button_bootstrap, QtCore.Qt.LeftButton)
assert autoclose_dialog.dialogs == [
("Information", "The organization and the user have been created. You can now login.")
]

0 comments on commit f71f79c

Please sign in to comment.