HPI/my/core/tests/test_config.py

178 lines
5.1 KiB
Python

"""
Various tests that are checking behaviour of user config wrt to various things
"""
import sys
import os
from pathlib import Path
import pytest
import pytz
import my.config
from my.core import notnone
from my.demo import items, make_config
from .common import tmp_environ_set
# TODO would be nice to randomize test order here to catch various config issues
# run the same test multiple times to make sure there are not issues with import order etc
@pytest.mark.parametrize('run_id', ['1', '2'])
def test_override_config(tmp_path: Path, run_id: str) -> None:
class user_config:
username = f'user_{run_id}'
data_path = f'{tmp_path}/*.json'
my.config.demo = user_config # type: ignore[misc, assignment]
[item1, item2] = items()
assert item1.username == f'user_{run_id}'
assert item2.username == f'user_{run_id}'
@pytest.mark.skip(reason="won't work at the moment because of inheritance")
def test_dynamic_config_simplenamespace(tmp_path: Path) -> None:
from types import SimpleNamespace
user_config = SimpleNamespace(
username='user3',
data_path=f'{tmp_path}/*.json',
)
my.config.demo = user_config # type: ignore[misc, assignment]
cfg = make_config()
assert cfg.username == 'user3'
def test_mixin_attribute_handling(tmp_path: Path) -> None:
"""
Tests that arbitrary mixin attributes work with our config handling pattern
"""
nytz = pytz.timezone('America/New_York')
class user_config:
# check that override is taken into the account
timezone = nytz
irrelevant = 'hello'
username = 'UUU'
data_path = f'{tmp_path}/*.json'
my.config.demo = user_config # type: ignore[misc, assignment]
cfg = make_config()
assert cfg.username == 'UUU'
# mypy doesn't know about it, but the attribute is there
assert getattr(cfg, 'irrelevant') == 'hello'
# check that overridden default attribute is actually getting overridden
assert cfg.timezone == nytz
[item1, item2] = items()
assert item1.username == 'UUU'
assert notnone(item1.dt.tzinfo).zone == nytz.zone # type: ignore[attr-defined]
assert item2.username == 'UUU'
assert notnone(item2.dt.tzinfo).zone == nytz.zone # type: ignore[attr-defined]
# use multiple identical tests to make sure there are no issues with cached imports etc
@pytest.mark.parametrize('run_id', ['1', '2'])
def test_dynamic_module_import(tmp_path: Path, run_id: str) -> None:
"""
Test for dynamic hackery in config properties
e.g. importing some external modules
"""
ext = tmp_path / 'external'
ext.mkdir()
(ext / '__init__.py').write_text(
'''
def transform(x):
from .submodule import do_transform
return do_transform(x)
'''
)
(ext / 'submodule.py').write_text(
f'''
def do_transform(x):
return {{"total_{run_id}": sum(x.values())}}
'''
)
class user_config:
username = 'someuser'
data_path = f'{tmp_path}/*.json'
external = f'{ext}'
my.config.demo = user_config # type: ignore[misc, assignment]
[item1, item2] = items()
assert item1.raw == {f'total_{run_id}': 1 + 123}, item1
assert item2.raw == {f'total_{run_id}': 2 + 456}, item2
# need to reset these modules, otherwise they get cached
# kind of relevant to my.core.cfg.tmp_config
sys.modules.pop('external', None)
sys.modules.pop('external.submodule', None)
@pytest.mark.parametrize('run_id', ['1', '2'])
def test_my_config_env_variable(tmp_path: Path, run_id: str) -> None:
"""
Tests handling of MY_CONFIG variable
"""
# ugh. so by this point, my.config is already loaded (default stub), so we need to unload it
sys.modules.pop('my.config', None)
# but my.config itself relies on my.core.init hook, so unless it's reloaded too it wouldn't help
sys.modules.pop('my.core', None)
sys.modules.pop('my.core.init', None)
# it's a bit of a mouthful of course, but in most cases MY_CONFIG would be set once
# , and before hpi runs, so hopefully it's not a huge deal
cfg_dir = tmp_path / 'my'
cfg_file = cfg_dir / 'config.py'
cfg_dir.mkdir()
cfg_file.write_text(
f'''
# print("IMPORTING CONFIG {run_id}")
class demo:
username = 'xxx_{run_id}'
data_path = r'{tmp_path}{os.sep}*.json' # need raw string for windows...
'''
)
with tmp_environ_set('MY_CONFIG', str(tmp_path)):
[item1, item2] = items()
assert item1.username == f'xxx_{run_id}'
assert item2.username == f'xxx_{run_id}'
# sigh.. so this is cached in sys.path
# so it takes precedence later during next import, not giving the MY_CONFIG hook
# (imported from builtin my.config) to kick in
sys.path.remove(str(tmp_path))
# FIXME ideally this shouldn't be necessary?
# remove this after we fixup my.tests.reddit and my.tests.commits
# (they were failing ci when running all tests)
sys.modules.pop('my.config', None)
@pytest.fixture(autouse=True)
def prepare_data(tmp_path: Path):
(tmp_path / 'data.json').write_text(
'''
[
{"key": 1, "value": 123},
{"key": 2, "value": 456}
]
'''
)