Skip to content

Commit 7319cc6

Browse files
committed
Introduce caching of configured live accounts in each test process
1 parent c8bfa98 commit 7319cc6

File tree

3 files changed

+98
-7
lines changed

3 files changed

+98
-7
lines changed

python/mypy.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[mypy]
22

3+
[mypy-py.*]
4+
ignore_missing_imports = True
5+
36
[mypy-deltachat.capi.*]
47
ignore_missing_imports = True
58

python/src/deltachat/testplugin.py

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import pytest
1515
import requests
16+
import py
1617

1718
from . import Account, const, account_hookimpl, get_core_info
1819
from .events import FFIEventLogger, FFIEventTracker
@@ -178,6 +179,50 @@ def get_liveconfig_producer(self):
178179
yield config
179180
pytest.fail("more than {} live accounts requested.".format(MAX_LIVE_CREATED_ACCOUNTS))
180181

182+
def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path):
183+
db_target_path = py.path.local(db_target_path)
184+
assert not db_target_path.exists()
185+
186+
print("checking cache for", cache_addr)
187+
try:
188+
filescache = self._addr2files[cache_addr]
189+
except KeyError:
190+
print("CACHE FAIL for", cache_addr)
191+
return False
192+
else:
193+
print("CACHE HIT for", cache_addr)
194+
targetdir = db_target_path.dirpath()
195+
write_dict_to_dir(filescache, targetdir)
196+
return True
197+
198+
def cache_maybe_store_configured_db_files(self, acc):
199+
addr = acc.get_config("addr")
200+
assert acc.is_configured()
201+
# don't overwrite existing entries
202+
if addr not in self._addr2files:
203+
print("storing cache for", addr)
204+
basedir = py.path.local(acc.get_blobdir()).dirpath()
205+
self._addr2files[addr] = create_dict_from_files_in_path(basedir)
206+
return True
207+
208+
209+
def create_dict_from_files_in_path(path):
210+
base = py.path.local(path)
211+
cachedict = {}
212+
for path in base.visit(fil=py.path.local.isfile):
213+
cachedict[path.relto(base)] = path.read_binary()
214+
return cachedict
215+
216+
217+
def write_dict_to_dir(dic, target_dir):
218+
assert dic
219+
target_dir = py.path.local(target_dir)
220+
for relpath, content in dic.items():
221+
path = target_dir.join(relpath)
222+
if not path.dirpath().exists():
223+
path.dirpath().ensure(dir=1)
224+
path.write_binary(content)
225+
181226

182227
@pytest.fixture
183228
def data(request):
@@ -225,6 +270,16 @@ def __init__(self, testprocess, init_time):
225270
self.testprocess = testprocess
226271
self.init_time = init_time
227272

273+
def log(self, *args):
274+
print("[acsetup]", "{:.3f}".format(time.time() - self.init_time), *args)
275+
276+
def add_configured(self, account):
277+
""" add an already configured account. """
278+
assert account.is_configured()
279+
self._account2state[account] = self.CONFIGURED
280+
self.log("added already configured account", account, account.get_config("addr"))
281+
return
282+
228283
def start_configure(self, account, reconfigure=False):
229284
""" add an account and start its configure process. """
230285
class PendingTracker:
@@ -235,7 +290,7 @@ def ac_configure_completed(this, success):
235290
account.add_account_plugin(PendingTracker(), name="pending_tracker")
236291
self._account2state[account] = self.CONFIGURING
237292
account.configure(reconfigure=reconfigure)
238-
print("started configure on pending", account)
293+
self.log("started configure on", account)
239294

240295
def wait_one_configured(self, account):
241296
""" wait until this account has successfully configured. """
@@ -328,6 +383,9 @@ def __init__(self, request, testprocess, tmpdir, data) -> None:
328383
self.set_logging_default(False)
329384
request.addfinalizer(self.finalize)
330385

386+
def log(self, *args):
387+
print("[acfactory]", "{:.3f}".format(time.time() - self.init_time), *args)
388+
331389
def finalize(self):
332390
while self._finalizers:
333391
fin = self._finalizers.pop()
@@ -359,9 +417,19 @@ def get_next_liveconfig(self):
359417
assert "addr" in configdict and "mail_pw" in configdict
360418
return configdict
361419

420+
def _get_cached_account(self, addr):
421+
if addr in self.testprocess._addr2files:
422+
return self._getaccount(addr)
423+
362424
def get_unconfigured_account(self):
425+
return self._getaccount()
426+
427+
def _getaccount(self, try_cache_addr=None):
363428
logid = "ac{}".format(len(self._accounts) + 1)
364-
path = self.tmpdir.join(logid)
429+
# we need to use fixed database basename for maybe_cache_* functions to work
430+
path = self.tmpdir.mkdir(logid).join("dc.db")
431+
if try_cache_addr:
432+
self.testprocess.cache_maybe_retrieve_configured_db_files(try_cache_addr, path)
365433
ac = Account(path.strpath, logging=self._logging)
366434
ac._logid = logid # later instantiated FFIEventLogger needs this
367435
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
@@ -394,7 +462,7 @@ def _preconfigure_key(self, account, addr):
394462
def get_pseudo_configured_account(self):
395463
# do a pseudo-configured account
396464
ac = self.get_unconfigured_account()
397-
acname = os.path.basename(ac.db_path)
465+
acname = ac._logid
398466
addr = "{}@offline.org".format(acname)
399467
ac.update_config(dict(
400468
addr=addr, displayname=acname, mail_pw="123",
@@ -405,7 +473,7 @@ def get_pseudo_configured_account(self):
405473
self._acsetup.init_logging(ac)
406474
return ac
407475

408-
def new_online_configuring_account(self, cloned_from=None, **kwargs):
476+
def new_online_configuring_account(self, cloned_from=None, cache=False, **kwargs):
409477
if cloned_from is None:
410478
configdict = self.get_next_liveconfig()
411479
else:
@@ -415,6 +483,12 @@ def new_online_configuring_account(self, cloned_from=None, **kwargs):
415483
mail_pw=cloned_from.get_config("mail_pw"),
416484
)
417485
configdict.update(kwargs)
486+
ac = self._get_cached_account(addr=configdict["addr"]) if cache else None
487+
if ac is not None:
488+
# make sure we consume a preconfig key, as if we had created a fresh account
489+
self._preconfigured_keys.pop(0)
490+
self._acsetup.add_configured(ac)
491+
return ac
418492
ac = self.prepare_account_from_liveconfig(configdict)
419493
self._acsetup.start_configure(ac)
420494
return ac
@@ -439,9 +513,11 @@ def bring_accounts_online(self):
439513
print("all accounts online")
440514

441515
def get_online_accounts(self, num):
442-
# to reduce number of log events logging starts after accounts can receive
443-
accounts = [self.new_online_configuring_account() for i in range(num)]
516+
accounts = [self.new_online_configuring_account(cache=True) for i in range(num)]
444517
self.bring_accounts_online()
518+
# we cache fully configured and started accounts
519+
for acc in accounts:
520+
self.testprocess.cache_maybe_store_configured_db_files(acc)
445521
return accounts
446522

447523
def run_bot_process(self, module, ffi=True):

python/tests/test_4_lowlevel.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from __future__ import print_function
1+
2+
import os
23

34
from queue import Queue
45
from deltachat import capi, cutil, const
@@ -46,6 +47,17 @@ def test_two_accounts_one_waited_all_started(self, monkeypatch, acfactory, testp
4647
assert pc._account2state[ac1] == pc.IDLEREADY
4748
assert pc._account2state[ac2] == pc.IDLEREADY
4849

50+
def test_store_and_retrieve_configured_account_cache(self, acfactory, tmpdir):
51+
ac1 = acfactory.get_pseudo_configured_account()
52+
holder = acfactory._acsetup.testprocess
53+
assert holder.cache_maybe_store_configured_db_files(ac1)
54+
assert not holder.cache_maybe_store_configured_db_files(ac1)
55+
acdir = tmpdir.mkdir("newaccount")
56+
addr = ac1.get_config("addr")
57+
target_db_path = acdir.join("db").strpath
58+
assert holder.cache_maybe_retrieve_configured_db_files(addr, target_db_path)
59+
assert len(os.listdir(acdir)) >= 2
60+
4961

5062
def test_liveconfig_caching(acfactory, monkeypatch):
5163
prod = [

0 commit comments

Comments
 (0)