diff --git a/my/core/cfg.py b/my/core/cfg.py index f298e7f..0b59537 100644 --- a/my/core/cfg.py +++ b/my/core/cfg.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TypeVar, Type, Callable, Dict, Any Attrs = Dict[str, Any] @@ -46,24 +48,29 @@ def _override_config(config: F) -> Iterator[F]: import importlib import sys -from typing import Optional, Set +from typing import Optional ModuleRegex = str @contextmanager def _reload_modules(modules: ModuleRegex) -> Iterator[None]: - def loaded_modules() -> Set[str]: - return {name for name in sys.modules if re.fullmatch(modules, name)} + # need to use list here, otherwise reordering with set might mess things up + def loaded_modules() -> list[str]: + return [name for name in sys.modules if re.fullmatch(modules, name)] modules_before = loaded_modules() - for m in modules_before: + # uhh... seems that reversed might make more sense -- not 100% sure why, but this works for tests/reddit.py + for m in reversed(modules_before): + # ugh... seems that reload works whereas pop doesn't work in some cases (e.g. on tests/reddit.py) + # sys.modules.pop(m, None) importlib.reload(sys.modules[m]) try: yield finally: modules_after = loaded_modules() + modules_before_set = set(modules_before) for m in modules_after: - if m in modules_before: + if m in modules_before_set: # was previously loaded, so need to reload to pick up old config importlib.reload(sys.modules[m]) else: diff --git a/tests/common.py b/my/tests/common.py similarity index 69% rename from tests/common.py rename to my/tests/common.py index 47c2991..c8d88ff 100644 --- a/tests/common.py +++ b/my/tests/common.py @@ -1,27 +1,29 @@ import os from pathlib import Path +import re +import sys import pytest V = 'HPI_TESTS_KARLICOSS' skip_if_not_karlicoss = pytest.mark.skipif( - V not in os.environ, reason=f'test only works on @karlicoss data for now. Set evn variable {V}=true to override.', + V not in os.environ, + reason=f'test only works on @karlicoss data for now. Set evn variable {V}=true to override.', ) + def reset_modules() -> None: ''' A hack to 'unload' HPI modules, otherwise some modules might cache the config TODO: a bit crap, need a better way.. ''' - import sys - import re to_unload = [m for m in sys.modules if re.match(r'my[.]?', m)] for m in to_unload: del sys.modules[m] def testdata() -> Path: - d = Path(__file__).absolute().parent.parent / 'testdata' + d = Path(__file__).absolute().parent.parent.parent / 'testdata' assert d.exists(), d return d diff --git a/tests/reddit.py b/my/tests/reddit.py similarity index 66% rename from tests/reddit.py rename to my/tests/reddit.py index 6e3e65e..0871041 100644 --- a/tests/reddit.py +++ b/my/tests/reddit.py @@ -1,5 +1,16 @@ -from datetime import datetime -import pytz +from datetime import datetime, timezone + +from my.core.cfg import tmp_config +from my.core.common import make_dict + +# todo ugh, it's discovered as a test??? +from .common import testdata + +import pytest + +# deliberately use mixed style imports on the top level and inside the methods to test tmp_config stuff +import my.reddit.rexport as my_reddit_rexport +import my.reddit.all as my_reddit_all def test_basic() -> None: @@ -7,17 +18,18 @@ def test_basic() -> None: # would ensure reasonable stat implementation as well and less duplication # note: deliberately use old module (instead of my.reddit.all) to test bwd compatibility from my.reddit import saved, events + assert len(list(events())) > 0 assert len(list(saved())) > 0 def test_comments() -> None: - from my.reddit.all import comments - assert len(list(comments())) > 0 + assert len(list(my_reddit_all.comments())) > 0 def test_unfav() -> None: - from my.reddit import events, saved + from my.reddit import events + ev = events() url = 'https://reddit.com/r/QuantifiedSelf/comments/acxy1v/personal_dashboard/' uev = [e for e in ev if e.url == url] @@ -31,52 +43,48 @@ def test_unfav() -> None: def test_saves() -> None: from my.reddit.all import saved + saves = list(saved()) assert len(saves) > 0 # just check that they are unique (makedict will throw) - from my.core.common import make_dict make_dict(saves, key=lambda s: s.sid) def test_disappearing() -> None: - from my.reddit.rexport import events # eh. so for instance, 'metro line colors' is missing from reddit-20190402005024.json for no reason # but I guess it was just a short glitch... so whatever - saves = events() - favs = [s.kind for s in saves if s.text == 'favorited'] + evs = my_reddit_rexport.events() + favs = [s.kind for s in evs if s.text == 'favorited'] [deal_with_it] = [f for f in favs if f.title == '"Deal with it!"'] - assert deal_with_it.backup_dt == datetime(2019, 4, 1, 23, 10, 25, tzinfo=pytz.utc) + assert deal_with_it.backup_dt == datetime(2019, 4, 1, 23, 10, 25, tzinfo=timezone.utc) def test_unfavorite() -> None: - from my.reddit.rexport import events - evs = events() + evs = my_reddit_rexport.events() unfavs = [s for s in evs if s.text == 'unfavorited'] [xxx] = [u for u in unfavs if u.eid == 'unf-19ifop'] - assert xxx.dt == datetime(2019, 1, 29, 10, 10, 20, tzinfo=pytz.utc) + assert xxx.dt == datetime(2019, 1, 29, 10, 10, 20, tzinfo=timezone.utc) def test_preserves_extra_attr() -> None: # doesn't strictly belong here (not specific to reddit) - # but my.reddit does a fair bit of dyunamic hacking, so perhaps a good place to check nothing is lost + # but my.reddit does a fair bit of dynamic hacking, so perhaps a good place to check nothing is lost from my.reddit import config + assert isinstance(getattr(config, 'please_keep_me'), str) -import pytest @pytest.fixture(autouse=True, scope='module') def prepare(): - from .common import testdata data = testdata() / 'hpi-testdata' / 'reddit' assert data.exists(), data # note: deliberately using old config schema so we can test migrations - class test_config: - export_dir = data - please_keep_me = 'whatever' + class config: + class reddit: + export_dir = data + please_keep_me = 'whatever' - from my.core.cfg import tmp_config - with tmp_config() as config: - config.reddit = test_config + with tmp_config(modules='my.reddit.*', config=config): yield diff --git a/tests/bluemaestro.py b/tests/bluemaestro.py index c932d73..84d3eb0 100644 --- a/tests/bluemaestro.py +++ b/tests/bluemaestro.py @@ -50,7 +50,7 @@ def test_old_db() -> None: @pytest.fixture(autouse=True) def prepare(): - from .common import testdata + from my.tests.common import testdata bmdata = testdata() / 'hpi-testdata' / 'bluemaestro' assert bmdata.exists(), bmdata diff --git a/tests/config.py b/tests/config.py index e69f726..101f7df 100644 --- a/tests/config.py +++ b/tests/config.py @@ -121,6 +121,6 @@ Some misc stuff @pytest.fixture(autouse=True) def prepare(): - from .common import reset_modules + from my.tests.common import reset_modules reset_modules() yield diff --git a/tests/emfit.py b/tests/emfit.py index 8a779e4..b316017 100644 --- a/tests/emfit.py +++ b/tests/emfit.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark def test() -> None: @@ -13,8 +13,6 @@ def test() -> None: assert d.sleep_end.tzinfo is not None -from .common import skip_if_not_karlicoss -@skip_if_not_karlicoss def test_tz() -> None: from my.emfit import datas # TODO check errors too? diff --git a/tests/foursquare.py b/tests/foursquare.py index a9169ff..a75190f 100644 --- a/tests/foursquare.py +++ b/tests/foursquare.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark def test_checkins() -> None: from my.foursquare import get_checkins diff --git a/tests/github.py b/tests/github.py index 5fb5fb9..6b7df23 100644 --- a/tests/github.py +++ b/tests/github.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark from more_itertools import ilen # todo test against stats? not sure.. maybe both diff --git a/tests/goodreads.py b/tests/goodreads.py index 9acab5c..79e638a 100644 --- a/tests/goodreads.py +++ b/tests/goodreads.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark from more_itertools import ilen diff --git a/tests/hypothesis.py b/tests/hypothesis.py index f5ee99e..8ca76dc 100644 --- a/tests/hypothesis.py +++ b/tests/hypothesis.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark def test() -> None: from my.hypothesis import pages, highlights diff --git a/tests/instapaper.py b/tests/instapaper.py index 153a716..862654d 100644 --- a/tests/instapaper.py +++ b/tests/instapaper.py @@ -1,4 +1,5 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark + def test_pages() -> None: # TODO ugh. need lazy import to simplify testing? diff --git a/tests/jawbone.py b/tests/jawbone.py index 776ac50..0a05e9c 100644 --- a/tests/jawbone.py +++ b/tests/jawbone.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark from datetime import date, time diff --git a/tests/lastfm.py b/tests/lastfm.py index 43e8f41..b9e8887 100644 --- a/tests/lastfm.py +++ b/tests/lastfm.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark # todo maybe belongs to common from more_itertools import ilen diff --git a/tests/orgmode.py b/tests/orgmode.py index d213a5e..37d783e 100644 --- a/tests/orgmode.py +++ b/tests/orgmode.py @@ -1,9 +1,9 @@ +from my.tests.common import skip_if_not_karlicoss as pytestmark + from my import orgmode from my.core.orgmode import collect -from .common import skip_if_not_karlicoss -@skip_if_not_karlicoss def test() -> None: # meh results = list(orgmode.query().collect_all(lambda n: [n] if 'python' in n.tags else [])) diff --git a/tests/pdfs.py b/tests/pdfs.py index 1c5eab8..63b1319 100644 --- a/tests/pdfs.py +++ b/tests/pdfs.py @@ -4,7 +4,7 @@ from more_itertools import ilen import pytest -from .common import testdata +from my.tests.common import testdata def test_module(with_config) -> None: @@ -35,7 +35,7 @@ def test_with_error(with_config, tmp_path: Path) -> None: @pytest.fixture def with_config(): - from .common import reset_modules + from my.tests.common import reset_modules reset_modules() # todo ugh.. getting boilerplaty.. need to make it a bit more automatic.. # extra_data = Path(__file__).absolute().parent / 'extra/data/polar' diff --git a/tests/rtm.py b/tests/rtm.py index 93378b6..621e471 100644 --- a/tests/rtm.py +++ b/tests/rtm.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark def test() -> None: diff --git a/tests/shared_config.py b/tests/shared_config.py index 6b83a5a..c2f6973 100644 --- a/tests/shared_config.py +++ b/tests/shared_config.py @@ -15,7 +15,7 @@ class SharedConfig(NamedTuple): def _prepare_google_config(tmp_path: Path): - from .common import testdata + from my.tests.common import testdata try: track = one(testdata().rglob('italy-slovenia-2017-07-29.json')) except ValueError: @@ -35,7 +35,7 @@ def _prepare_google_config(tmp_path: Path): # pass tmp_path from pytest to this helper function # see tests/tz.py as an example def temp_config(temp_path: Path) -> Any: - from .common import reset_modules + from my.tests.common import reset_modules reset_modules() LTZ.config.fast = True diff --git a/tests/smscalls.py b/tests/smscalls.py index 51150f0..d063de1 100644 --- a/tests/smscalls.py +++ b/tests/smscalls.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark # TODO maybe instead detect if it has any data at all # if none, then skip the test, say that user doesn't have any data? diff --git a/tests/tweets.py b/tests/tweets.py index fefc24e..763fcef 100644 --- a/tests/tweets.py +++ b/tests/tweets.py @@ -1,4 +1,4 @@ -from .common import skip_if_not_karlicoss as pytestmark +from my.tests.common import skip_if_not_karlicoss as pytestmark # todo current test doesn't depend on data, in principle... # should make lazy loading the default.. diff --git a/tests/youtube.py b/tests/youtube.py index 4864ee9..f37493b 100644 --- a/tests/youtube.py +++ b/tests/youtube.py @@ -1,3 +1,5 @@ +from my.tests.common import skip_if_not_karlicoss as pytestmark + # TODO move elsewhere? # these tests would only make sense with some existing data? although some of them would work for everyone.. # not sure what's a good way of handling this.. @@ -6,8 +8,6 @@ import pytz from more_itertools import bucket -from .common import skip_if_not_karlicoss as pytestmark - # TODO ugh. if i uncomment this here (on top level), then this test vvv fails # from my.media.youtube import get_watched, Watched # HPI_TESTS_KARLICOSS=true pytest -raps tests/tz.py tests/youtube.py diff --git a/tox.ini b/tox.ini index 3124618..9ec80f1 100644 --- a/tox.ini +++ b/tox.ini @@ -79,6 +79,13 @@ commands = hpi module install my.reddit.rexport + {envpython} -m pytest \ + # importlib is the new suggested import-mode + # without it test package names end up as core.tests.* instead of my.core.tests.* + --import-mode=importlib \ + --pyargs my.tests \ + {posargs} + {envpython} -m pytest tests \ # ignore some tests which might take a while to run on ci.. --ignore tests/takeout.py \