fix python lifecycles so that termination works

This commit is contained in:
holger krekel
2020-05-23 10:17:56 +02:00
parent 05f79c1c01
commit e55dc2213a
10 changed files with 102 additions and 115 deletions

View File

@@ -63,6 +63,10 @@ def run_cmdline(argv=None, account_plugins=None):
log = events.FFIEventLogger(ac, "bot")
ac.add_account_plugin(log)
for plugin in account_plugins or []:
print("adding plugin", plugin)
ac.add_account_plugin(plugin)
if not ac.is_configured():
assert args.email and args.password, (
"you must specify --email and --password once to configure this database/account"
@@ -72,12 +76,11 @@ def run_cmdline(argv=None, account_plugins=None):
ac.set_config("mvbox_move", "0")
ac.set_config("mvbox_watch", "0")
ac.set_config("sentbox_watch", "0")
for plugin in account_plugins or []:
ac.add_account_plugin(plugin)
ac.configure()
ac.wait_configure_finish()
# start IO threads and configure if neccessary
ac.start()
ac.start_io()
print("{}: waiting for message".format(ac.get_config("addr")))

View File

@@ -1,7 +1,6 @@
""" Account class implementation. """
from __future__ import print_function
import atexit
from contextlib import contextmanager
from email.utils import parseaddr
from threading import Event
@@ -48,7 +47,7 @@ class Account(object):
self._dc_context = ffi.gc(
lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL),
_destroy_dc_context,
lib.dc_context_unref,
)
if self._dc_context == ffi.NULL:
raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path))
@@ -58,7 +57,6 @@ class Account(object):
self._shutdown_event = Event()
self._event_thread = EventThread(self)
self._configkeys = self.get_config("sys.config_keys").split()
atexit.register(self.shutdown)
hook.dc_account_init(account=self)
def disable_logging(self):
@@ -69,8 +67,8 @@ class Account(object):
""" re-enable logging. """
self._logging = True
# def __del__(self):
# self.shutdown()
def __del__(self):
self.shutdown()
def log(self, msg):
if self._logging:
@@ -241,7 +239,7 @@ class Account(object):
:returns: True if deletion succeeded (contact was deleted)
"""
contact_id = contact.id
assert contact._dc_context == self._dc_context
assert contact.account == self
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
@@ -289,7 +287,7 @@ class Account(object):
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(contact, "id"):
if contact._dc_context != self._dc_context:
if contact.account != self:
raise ValueError("Contact belongs to a different Account")
contact_id = contact.id
else:
@@ -309,7 +307,7 @@ class Account(object):
:returns: a :class:`deltachat.chat.Chat` object.
"""
if hasattr(message, "id"):
if self._dc_context != message._dc_context:
if message.account != self:
raise ValueError("Message belongs to a different Account")
msg_id = message.id
else:
@@ -557,13 +555,14 @@ class Account(object):
""" Stop ongoing securejoin, configuration or other core jobs. """
lib.dc_stop_ongoing_process(self._dc_context)
def start(self):
def start_io(self):
""" start this account's IO scheduling (Rust-core async scheduler)
If this account is not configured but "addr" and "mail_pw" config
values are set, dc_configure() will be called.
If this account is not configured an Exception is raised.
You need to call account.configure() and account.wait_configure_finish()
before.
You may call `wait_shutdown` or `shutdown` after the
You may call `stop_scheduler`, `wait_shutdown` or `shutdown` after the
account is started.
:raises MissingCredentials: if `addr` and `mail_pw` values are not set.
@@ -572,8 +571,7 @@ class Account(object):
:returns: None (account is configured and with io-scheduling running)
"""
if not self.is_configured():
self.configure()
self.wait_configure_finish()
raise ValueError("account not configured, cannot start io")
lib.dc_start_io(self._dc_context)
def configure(self):
@@ -601,13 +599,13 @@ class Account(object):
""" wait until shutdown of this account has completed. """
self._shutdown_event.wait()
def stop_scheduler(self):
""" stop core scheduler if it is running. """
def stop_io(self):
""" stop core IO scheduler if it is running. """
self.log("stop_ongoing")
self.stop_ongoing()
if bool(lib.dc_is_io_running(self._dc_context)):
self.log("context_shutdown (stop core scheduler)")
self.log("dc_stop_io (stop core IO scheduler)")
lib.dc_stop_io(self._dc_context)
else:
self.log("stop_scheduler called on non-running context")
@@ -615,13 +613,12 @@ class Account(object):
def shutdown(self):
""" shutdown and destroy account (stop callback thread, close and remove
underlying dc_context)."""
dc_context = self._dc_context
if dc_context is None:
if self._dc_context is None:
return
self.stop_scheduler()
self.stop_io()
self.log("remove dc_context")
self.log("remove dc_context references")
# the dc_context_unref triggers get_next_event to return ffi.NULL
# which in turns makes the event thread finish execution
self._dc_context = None
@@ -629,18 +626,13 @@ class Account(object):
self.log("wait for event thread to finish")
self._event_thread.wait()
atexit.unregister(self.shutdown)
self._shutdown_event.set()
hook = hookspec.Global._get_plugin_manager().hook
hook.dc_account_after_shutdown(account=self, dc_context=dc_context)
hook.dc_account_after_shutdown(account=self)
self.log("shutdown finished")
def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
# destructor for dc_context
dc_context_unref(dc_context)
class ScannedQRCode:
def __init__(self, dc_lot):
self._dc_lot = dc_lot

View File

@@ -19,12 +19,11 @@ class Chat(object):
def __init__(self, account, id):
self.account = account
self._dc_context = account._dc_context
self.id = id
def __eq__(self, other):
return self.id == getattr(other, "id", None) and \
self._dc_context == getattr(other, "_dc_context", None)
self.account._dc_context == other.account._dc_context
def __ne__(self, other):
return not (self == other)
@@ -35,7 +34,7 @@ class Chat(object):
@property
def _dc_chat(self):
return ffi.gc(
lib.dc_get_chat(self._dc_context, self.id),
lib.dc_get_chat(self.account._dc_context, self.id),
lib.dc_chat_unref
)
@@ -47,7 +46,7 @@ class Chat(object):
- does not delete messages on server
- the chat or contact is not blocked, new message will arrive
"""
lib.dc_delete_chat(self._dc_context, self.id)
lib.dc_delete_chat(self.account._dc_context, self.id)
# ------ chat status/metadata API ------------------------------
@@ -105,7 +104,7 @@ class Chat(object):
:returns: None
"""
name = as_dc_charpointer(name)
return lib.dc_set_chat_name(self._dc_context, self.id, name)
return lib.dc_set_chat_name(self.account._dc_context, self.id, name)
def mute(self, duration=None):
""" mutes the chat
@@ -117,7 +116,7 @@ class Chat(object):
mute_duration = -1
else:
mute_duration = duration
ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration)
ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration)
if not bool(ret):
raise ValueError("Call to dc_set_chat_mute_duration failed")
@@ -126,7 +125,7 @@ class Chat(object):
:returns: None
"""
ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0)
ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, 0)
if not bool(ret):
raise ValueError("Failed to unmute chat")
@@ -152,7 +151,7 @@ class Chat(object):
in a second channel (typically used by mobiles with QRcode-show + scan UX)
where account.join_with_qrcode(qr) needs to be called.
"""
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
res = lib.dc_get_securejoin_qr(self.account._dc_context, self.id)
return from_dc_charpointer(res)
# ------ chat messaging API ------------------------------
@@ -174,7 +173,7 @@ class Chat(object):
assert msg.id != 0
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, msg.id)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
# modify message in place to avoid bad state for the caller
@@ -189,7 +188,7 @@ class Chat(object):
:returns: the resulting :class:`deltachat.message.Message` instance
"""
msg = as_dc_charpointer(text)
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
msg_id = lib.dc_send_text_msg(self.account._dc_context, self.id, msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self.account, msg_id)
@@ -204,7 +203,7 @@ class Chat(object):
"""
msg = Message.new_empty(self.account, view_type="file")
msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
@@ -219,7 +218,7 @@ class Chat(object):
mime_type = mimetypes.guess_type(path)[0]
msg = Message.new_empty(self.account, view_type="image")
msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
@@ -230,7 +229,7 @@ class Chat(object):
:param msg: the message to be prepared.
:returns: :class:`deltachat.message.Message` instance.
"""
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
msg_id = lib.dc_prepare_msg(self.account._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared")
# invalidate passed in message which is not safe to use anymore
@@ -266,7 +265,7 @@ class Chat(object):
msg = Message.from_db(self.account, message.id)
# pass 0 as chat-id because core-docs say it's ok when out-preparing
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
sent_id = lib.dc_send_msg(self.account._dc_context, 0, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
assert sent_id == msg.id
@@ -280,9 +279,9 @@ class Chat(object):
:returns: None
"""
if message is None:
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
lib.dc_set_draft(self.account._dc_context, self.id, ffi.NULL)
else:
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
lib.dc_set_draft(self.account._dc_context, self.id, message._dc_msg)
def get_draft(self):
""" get draft message for this chat.
@@ -290,7 +289,7 @@ class Chat(object):
:param message: a :class:`Message` instance
:returns: Message object or None (if no draft available)
"""
x = lib.dc_get_draft(self._dc_context, self.id)
x = lib.dc_get_draft(self.account._dc_context, self.id)
if x == ffi.NULL:
return None
dc_msg = ffi.gc(x, lib.dc_msg_unref)
@@ -302,7 +301,7 @@ class Chat(object):
:returns: list of :class:`deltachat.message.Message` objects for this chat.
"""
dc_array = ffi.gc(
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_get_chat_msgs(self.account._dc_context, self.id, 0, 0),
lib.dc_array_unref
)
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
@@ -312,18 +311,18 @@ class Chat(object):
:returns: number of fresh messages
"""
return lib.dc_get_fresh_msg_cnt(self._dc_context, self.id)
return lib.dc_get_fresh_msg_cnt(self.account._dc_context, self.id)
def mark_noticed(self):
""" mark all messages in this chat as noticed.
Noticed messages are no longer fresh.
"""
return lib.dc_marknoticed_chat(self._dc_context, self.id)
return lib.dc_marknoticed_chat(self.account._dc_context, self.id)
def get_summary(self):
""" return dictionary with summary information. """
dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id)
dc_res = lib.dc_chat_get_info_json(self.account._dc_context, self.id)
s = from_dc_charpointer(dc_res)
return json.loads(s)
@@ -336,7 +335,7 @@ class Chat(object):
:raises ValueError: if contact could not be added
:returns: None
"""
ret = lib.dc_add_contact_to_chat(self._dc_context, self.id, contact.id)
ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact))
@@ -347,7 +346,7 @@ class Chat(object):
:raises ValueError: if contact could not be removed
:returns: None
"""
ret = lib.dc_remove_contact_from_chat(self._dc_context, self.id, contact.id)
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))
@@ -359,7 +358,7 @@ class Chat(object):
"""
from .contact import Contact
dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id),
lib.dc_get_chat_contacts(self.account._dc_context, self.id),
lib.dc_array_unref
)
return list(iter_array(
@@ -378,7 +377,7 @@ class Chat(object):
"""
assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
@@ -391,7 +390,7 @@ class Chat(object):
:raises ValueError: if profile image could not be reset
:returns: None
"""
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, ffi.NULL)
if res != 1:
raise ValueError("Removing Profile Image failed")
@@ -421,7 +420,7 @@ class Chat(object):
"""return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled.
"""
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id)
return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id)
def is_archived(self):
"""return True if this chat is archived.
@@ -434,7 +433,7 @@ class Chat(object):
all subsequent messages will carry a location with them.
"""
lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds)
lib.dc_send_locations_to_chat(self.account._dc_context, self.id, seconds)
def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None):
"""return list of locations for the given contact in the given timespan.
@@ -458,7 +457,7 @@ class Chat(object):
else:
contact_id = contact.id
dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to)
dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to)
return [
Location(
latitude=lib.dc_array_get_latitude(dc_array, i),

View File

@@ -12,22 +12,21 @@ class Contact(object):
"""
def __init__(self, account, id):
self.account = account
self._dc_context = account._dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
return self.account._dc_context == other.account._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self.account._dc_context)
@property
def _dc_contact(self):
return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id),
lib.dc_get_contact(self.account._dc_context, self.id),
lib.dc_contact_unref
)

View File

@@ -133,8 +133,8 @@ class EventThread(threading.Thread):
"""
def __init__(self, account):
self.account = account
self._dc_context = account._dc_context
super(EventThread, self).__init__(name="events")
self.setDaemon(True)
self.start()
@contextmanager
@@ -157,7 +157,7 @@ class EventThread(threading.Thread):
def _inner_run(self):
event_emitter = ffi.gc(
lib.dc_get_event_emitter(self._dc_context),
lib.dc_get_event_emitter(self.account._dc_context),
lib.dc_event_emitter_unref,
)
while 1:
@@ -176,11 +176,15 @@ class EventThread(threading.Thread):
lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
for name, kwargs in self._map_ffi_event(ffi_event):
# self.account.log("calling hook name={} kwargs={}".format(name, kwargs))
hook = getattr(self.account._pm.hook, name)
hook(**kwargs)
try:
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
for name, kwargs in self._map_ffi_event(ffi_event):
self.account.log("calling hook name={} kwargs={}".format(name, kwargs))
hook = getattr(self.account._pm.hook, name)
hook(**kwargs)
except Exception:
if self.account._dc_context is not None:
raise
def _map_ffi_event(self, ffi_event):
name = ffi_event.name

View File

@@ -89,5 +89,5 @@ class Global:
""" called when `Account::__init__()` function starts executing. """
@global_hookspec
def dc_account_after_shutdown(self, account, dc_context):
def dc_account_after_shutdown(self, account):
""" Called after the account has been shutdown. """

View File

@@ -16,8 +16,7 @@ class Message(object):
"""
def __init__(self, account, dc_msg):
self.account = account
self._dc_context = account._dc_context
assert isinstance(self._dc_context, ffi.CData)
assert isinstance(self.account._dc_context, ffi.CData)
assert isinstance(dc_msg, ffi.CData)
assert dc_msg != ffi.NULL
self._dc_msg = dc_msg
@@ -58,7 +57,7 @@ class Message(object):
"""
self.account.create_chat_by_message(self)
self._dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_get_msg(self.account._dc_context, self.id),
lib.dc_msg_unref
)
@@ -118,12 +117,12 @@ class Message(object):
The text is multiline and may contain eg. the raw text of the message.
"""
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id))
return from_dc_charpointer(lib.dc_get_msg_info(self.account._dc_context, self.id))
def continue_key_transfer(self, setup_code):
""" extract key and use it as primary key for this account. """
res = lib.dc_continue_key_transfer(
self._dc_context,
self.account._dc_context,
self.id,
as_dc_charpointer(setup_code)
)
@@ -158,7 +157,7 @@ class Message(object):
:returns: email-mime message object (with headers only, no body).
"""
import email.parser
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id)
if mime_headers:
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
if isinstance(s, bytes):
@@ -201,7 +200,7 @@ class Message(object):
else:
# load message from db to get a fresh/current state
dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_get_msg(self.account._dc_context, self.id),
lib.dc_msg_unref
)
return lib.dc_msg_get_state(dc_msg)

View File

@@ -103,7 +103,6 @@ def pytest_report_header(config, startdir):
t = tempfile.mktemp()
m = MonkeyPatch()
try:
m.setattr(sys.stdout, "write", lambda x: len(x))
ac = Account(t)
info = ac.get_info()
ac.shutdown()
@@ -310,16 +309,16 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
ac1 = self.get_online_configuring_account(
pre_generated_key=pre_generated_key, mvbox=mvbox, move=move)
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
return ac1
def get_two_online_accounts(self, move=False, quiet=False):
ac1 = self.get_online_configuring_account(move=True, quiet=quiet)
ac2 = self.get_online_configuring_account(quiet=quiet)
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
return ac1, ac2
def clone_online_account(self, account, pre_generated_key=True):
@@ -394,6 +393,7 @@ class BotProcess:
break
line = line.strip()
self.stdout_queue.put(line)
print("bot-stdout: ", line)
finally:
self.stdout_queue.put(None)

View File

@@ -533,9 +533,9 @@ class TestOnlineAccount:
# rsa key gen can be slow especially on CI, adjust timeout
ac1._evtracker.set_timeout(120)
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
chat = self.get_chat(ac1, ac2, both_created=True)
lp.sec("ac1: send unencrypted message to ac2")
@@ -591,11 +591,11 @@ class TestOnlineAccount:
ac1_clone = acfactory.clone_online_account(ac1)
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
ac1_clone.wait_configure_finish()
ac1_clone.start()
ac1_clone.start_io()
chat = self.get_chat(ac1, ac2)
@@ -700,11 +700,11 @@ class TestOnlineAccount:
lp.sec("ac2: waiting for configuration")
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
lp.sec("ac1: waiting for configuration")
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
lp.sec("ac1: send message and wait for ac2 to receive it")
chat = self.get_chat(ac1, ac2)
@@ -717,9 +717,9 @@ class TestOnlineAccount:
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
@@ -731,9 +731,9 @@ class TestOnlineAccount:
ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account()
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
chat = self.get_chat(ac1, ac2)
chat.send_text("message1")
@@ -1162,9 +1162,9 @@ class TestOnlineAccount:
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.clone_online_account(ac1)
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
lp.sec("trigger ac setup message and return setupcode")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
@@ -1188,9 +1188,9 @@ class TestOnlineAccount:
ac2 = acfactory.clone_online_account(ac1)
ac2._evtracker.set_timeout(30)
ac2.wait_configure_finish()
ac2.start()
ac2.start_io()
ac1.wait_configure_finish()
ac1.start()
ac1.start_io()
lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
@@ -1502,7 +1502,7 @@ class TestGroupStressTests:
accounts = [acfactory.get_online_configuring_account() for i in range(5)]
for acc in accounts:
acc.wait_configure_finish()
acc.start()
acc.start_io()
ac1 = accounts.pop()
lp.sec("ac1: setting up contacts with 4 other members")
@@ -1610,7 +1610,7 @@ class TestGroupStressTests:
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
for acc in accounts:
acc.wait_configure_finish()
acc.start()
acc.start_io()
ac1 = accounts.pop()
lp.sec("ac1: setting up contacts with 2 other members")

View File

@@ -36,13 +36,14 @@ def test_wrong_db(tmpdir):
# write an invalid database file
p.write("x123" * 10)
assert ffi.NULL == lib.dc_context_new(ffi.NULL, ffi.NULL, p.strpath.encode("ascii"), ffi.NULL)
assert ffi.NULL == lib.dc_context_new(ffi.NULL, p.strpath.encode("ascii"), ffi.NULL)
def test_empty_blobdir(tmpdir):
db_fname = tmpdir.join("hello.db")
# Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), b""),
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), b""),
lib.dc_context_unref,
)
assert ctx != ffi.NULL
@@ -86,26 +87,16 @@ def test_get_special_message_id_returns_empty_message(acfactory):
def test_provider_info_none():
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL),
lib.dc_context_new(ffi.NULL, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' not in info
def test_get_info_open(tmpdir):
db_fname = tmpdir.join("test.db")
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL),
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))