Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

import os
import random
import subprocess
import sys
from typing import AsyncGenerator, Optional

import execnet
import py
import pytest

Expand Down Expand Up @@ -197,3 +200,102 @@ def indent(self, msg: str) -> None:
print(" " + msg)

return Printer()


#
# support for testing against different deltachat-rpc-server/clients
# installed into a temporary virtualenv and connected via 'execnet' channels
#


@pytest.fixture(scope="session")
def get_core_python_env(tmp_path_factory):
envs = {}

def get_core_python(core_version):
venv = envs.get(core_version)
if venv:
return venv
venv = tmp_path_factory.mktemp(f"temp-{core_version}")
python = sys.executable
subprocess.check_call([python, "-m", "venv", venv])
pip = venv.joinpath("bin", "pip")
pkgs = [f"deltachat-rpc-server=={core_version}", f"deltachat-rpc-client=={core_version}"]
subprocess.check_call([pip, "install", "pytest"] + pkgs)

envs[core_version] = venv
return venv

return get_core_python


@pytest.fixture
def alice_and_remote_bob(tmp_path, acfactory, get_core_python_env):
"""return local Alice account, a contact to bob, and a remote 'eval' function for bob.

The 'eval' function allows to remote-execute arbitrary expressions
that can use the `bob` online account, and the `bob_contact_alice`.
"""

def factory(core_version):
venv = get_core_python_env(core_version)
python = venv.joinpath("bin", "python")
gw = execnet.makegateway(f"popen//python={python}")

accounts_dir = str(tmp_path.joinpath("account1_venv1"))
channel = gw.remote_exec(remote_bob_loop)
cm = os.environ.get("CHATMAIL_DOMAIN")

# trigger getting an online account on bob's side
channel.send((accounts_dir, str(venv.joinpath("bin", "deltachat-rpc-server")), cm))

# meanwhile get a local alice account
alice = acfactory.get_online_account()
channel.send(alice.self_contact.make_vcard())

# wait for bob to have started
sysinfo = channel.receive()
assert sysinfo == f"v{core_version}"
bob_vcard = channel.receive()
[alice_contact_bob] = alice.import_vcard(bob_vcard)

def eval(eval_str):
channel.send(eval_str)
return channel.receive()

return alice, alice_contact_bob, eval

return factory


def remote_bob_loop(channel):
import os
import pathlib

from deltachat_rpc_client import DeltaChat, Rpc
from deltachat_rpc_client.pytestplugin import ACFactory

accounts_dir, rpc_server_path, chatmail_domain = channel.receive()
os.environ["CHATMAIL_DOMAIN"] = chatmail_domain
bin_path = str(pathlib.Path(rpc_server_path).parent)
os.environ["PATH"] = bin_path + ":" + os.environ["PATH"]

rpc = Rpc(accounts_dir=accounts_dir)
with rpc:
dc = DeltaChat(rpc)
channel.send(dc.rpc.get_system_info()["deltachat_core_version"])
acfactory = ACFactory(dc)
bob = acfactory.get_online_account()
alice_vcard = channel.receive()
[alice_contact] = bob.import_vcard(alice_vcard)
ns = {"bob": bob, "bob_contact_alice": alice_contact}
channel.send(bob.self_contact.make_vcard())

while 1:
eval_str = channel.receive()
res = eval(eval_str, ns)
try:
channel.send(res)
except Exception:
# some unserializable result
channel.send(None)
27 changes: 10 additions & 17 deletions deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def rpc_future():
class Rpc:
"""RPC client."""

def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
def __init__(self, accounts_dir: Optional[str] = None, rpc_server_path="deltachat-rpc-server", **kwargs):
"""Initialize RPC client.

The given arguments will be passed to subprocess.Popen().
Expand All @@ -66,6 +66,7 @@ def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
}

self._kwargs = kwargs
self.rpc_server_path = rpc_server_path
self.process: subprocess.Popen
self.id_iterator: Iterator[int]
self.event_queues: dict[int, Queue]
Expand All @@ -79,24 +80,16 @@ def __init__(self, accounts_dir: Optional[str] = None, **kwargs):

def start(self) -> None:
"""Start RPC server subprocess."""
popen_kwargs = {"stdin": subprocess.PIPE, "stdout": subprocess.PIPE}
if sys.version_info >= (3, 11):
self.process = subprocess.Popen(
"deltachat-rpc-server",
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
# Prevent subprocess from capturing SIGINT.
process_group=0,
**self._kwargs,
)
# Prevent subprocess from capturing SIGINT.
popen_kwargs["process_group"] = 0
else:
self.process = subprocess.Popen(
"deltachat-rpc-server",
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
# `process_group` is not supported before Python 3.11.
preexec_fn=os.setpgrp, # noqa: PLW1509
**self._kwargs,
)
# `process_group` is not supported before Python 3.11.
popen_kwargs["preexec_fn"] = os.setpgrp # noqa: PLW1509

popen_kwargs.update(self._kwargs)
self.process = subprocess.Popen(self.rpc_server_path, **popen_kwargs)
self.id_iterator = itertools.count(start=1)
self.event_queues = {}
self.request_results = {}
Expand Down
28 changes: 28 additions & 0 deletions deltachat-rpc-client/tests/test_cross_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest


@pytest.mark.parametrize("version", ["2.20.0", "2.10.0"])
def test_qr_setup_contact(alice_and_remote_bob, version) -> None:
alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)

qr_code = alice.get_qr_code()
remote_eval(f"bob.secure_join({qr_code!r})")
alice.wait_for_securejoin_inviter_success()

# Test that Alice verified Bob's profile.
alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
assert alice_contact_bob_snapshot.is_verified

remote_eval("bob.wait_for_securejoin_joiner_success()")

# Test that Bob verified Alice's profile.
assert remote_eval("bob_contact_alice.get_snapshot().is_verified")


def test_send_and_receive_message(alice_and_remote_bob) -> None:
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")

remote_eval("bob_contact_alice.create_chat().send_text('hello')")

msg = alice.wait_for_incoming_msg()
assert msg.get_snapshot().text == "hello"
16 changes: 16 additions & 0 deletions deltachat-rpc-client/tests/test_rpc_virtual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import subprocess
import sys

from deltachat_rpc_client import DeltaChat, Rpc


def test_install_venv_and_use_other_core(tmp_path):
venv = tmp_path.joinpath("venv1")
python = sys.executable
subprocess.check_call([python, "-m", "venv", venv])
subprocess.check_call([venv / "bin" / "pip", "install", "deltachat-rpc-server==2.20.0"])
rpc = Rpc(accounts_dir=tmp_path.joinpath("accounts"), rpc_server_path=venv.joinpath("bin", "deltachat-rpc-server"))

with rpc:
dc = DeltaChat(rpc)
assert dc.rpc.get_system_info()["deltachat_core_version"] == "v2.20.0"
Loading